Serdar YILMAZ / Software Developer
Bizimkisi Bir “Bug” Hikayesi...
Akbank Bankacılık Merkezi

Arşiv 'not' Kategori

C# – Katmanlı Mimaride Generic’ler

.net
Generic sınıfların, metotların ve arayüzlerin nasıl oluşturulduğundan (bkz: Generic Sınıflar, Metotlar ve Arayüzler) ve ne tür kısıtlar eklenebileceğinden (bkz: Generic Kısıtlar) bir önceki yazılarımızda bahsetmiştik. Bu içerikte ise Generic’lerin gerçek bir projede ne amaçla ve nasıl kullanılabileceğini olabildiğince yalın ve anlaşılır bir şekilde aktarmaya çalışacağım.

Örnek Senaryo: Müşteri, Ürün ve Kategori bilgilerini tutan Customer, Product ve Category isimli sınıflarımız olduğunu farz edelim. Müşterileri, Ürünleri ve Kategorileri listeleyecek, arama ve silme yapabilecek Manager sınıfları, Generic’leri kullanarak oluşturmaya çalışalım.

public class Customer
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public string Address { get; set; }
}

Müşterinin Id, isim ve adres bilgilerini tutan Customer isimli bir sınıfımız olsun.
public interface ICustomerManager
{
    List<Customer> List();
    Customer Find(int id);
    bool Remove(Customer customer);
}

Müşterileri listeleyecek, silecek ve müşteri listesinde arama yapabilecek metotlara ihtiyacımız olduğunu farz edelim. Bu ihtiyaçları ICustomerManager arayüzünde bildiriyoruz.
public class CustomerManager : ICustomerManager
{
    private static List<Customer> _customers = new List<Customer>() {
        new Customer { Id = 1, FullName = "Serdar YILMAZ", Address = "Kocaeli" },
        new Customer { Id = 2, FullName = "Behzat GÖK", Address = "Sakarya" },
        new Customer { Id = 3, FullName = "Sinem ER", Address = "Bolu" }
    };

    public List<Customer> List()
    {
        return _customers;
    }

    public Customer Find(int id)
    {
        return _customers.Find(x => x.Id == id);
    }

    public bool Remove(Customer customer)
    {
        return _customers.Remove(customer);
    }
}

ICustomerManager arayüzünde bildirimini yapmış olduğumuz metotları, CustomerManager isimli sınıf içerisinde tanımlıyoruz. CustomerManager sınıfında arama, silme ve listeleme işlemlerini _customers koleksiyonu üzerinde yapmaktayım, sizler geliştireceğiniz uygulamalarda bu işlemleri veritabanı üzerinde yapabilirsiniz.

Şimdi ise Ürün ve Kategori bilgilerini tutan Product ve Category sınıflarına ihtiyacımız olduğunu düşünelim.

public class Product
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Explanation { get; set; }

}

public class Category
{
    public int Id { get; set; }
    public string Title { get; set; }
}

Müşterilerde olduğu gibi Kategorilerde ve Ürünlerde de listeleme, silme ve arama işlemlerini yapacak metotlara ihtiyacımız olacaktır. Bu ihtiyaçları IProductManager ve ICategoryManager arayüzlerinde bildiriyoruz.
public interface IProductManager
{
    List<Product> List();
    Product Find(int id);
    bool Remove(Product product);
}

public interface ICategoryManager
{
    List<Category> List();
    Category Find(int id);
    bool Remove(Category category);
}

Oluşturmuş olduğumuz arayüzleri inceleyecek olursak; her üç arayüzde de(ICustomerManager, IProductManager, ICategoryManager) List, Find ve Remove metotlarının bildiriminin yapıldığını ve sadece parametre türlerinin ve metotların geri dönüş türlerinin farklılık gösterdiğini görebiliriz. O halde Generic bir arayüz oluşturup, parametre türlerini ve metotların geri dönüş türlerini parametrik hale getirebiliriz.
public interface IRepository<T>
{
    List<T> List();
    T Find(int id);
    bool Remove(T entity); 
}

Generic IRepository<T> arayüzü sayesinde, Manager sınıflar (CustomerManager, ProductManager, CategoryManager) için oluşturduğumuz arayüzlerin (ICustomerManager, IProductManager, ICategoryManager) hepsinde aynı metotların bildirimini yapmak zorunda kalmayacağız. Artık tüm Manager sınıflarda bulunması gereken ortak metotların bildirimini IRepository<T> arayüzünde yapacağız. Manager sınıfa özgü olan metotların bildirimini de o Manager sınıf için oluşturduğumuz arayüzün içerisinde yapacağız.
public interface IRepository<T> 
{
    List<T> List();
    T Find(int id);
    bool Remove(T entity);
}

public interface ICustomerManager : IRepository<Customer> { }
public interface ICategoryManager : IRepository<Category> { }

public class CustomerManager : ICustomerManager
{
    private static List<Customer> _customers = new List<Customer>() {
    new Customer { Id = 1, FullName = "Serdar YILMAZ", Address = "Kocaeli" },
    new Customer { Id = 2, FullName = "Behzat GÖK", Address = "Sakarya" },
    new Customer { Id = 3, FullName = "Sinem ER", Address = "Bolu" }
    };

    public List<Customer> List() { return _customers; }
    public Customer Find(int id) { return _customers.Find(x => x.Id == id); }
    public bool Remove(Customer customer) { return _customers.Remove(customer); }
}

public class CategoryManager : ICategoryManager
{
    private static List<Category> _category = new List<Category>() {
    new Category { Id = 1, Title="Yazılım"},
    new Category { Id = 2, Title="Network"},
    };

    public List<Category> List() { return _category; }
    public Category Find(int id) { return _category.Find(x => x.Id == id); }
    public bool Remove(Category category) { return _category.Remove(category); }
}

Kod kalabalığını arttırmamak için yukarıda ki kod bloğunda Product ve ProductManager sınıflarına yer vermedim. Yukarıdaki kod bloklarını inceledikten sonra aklınıza şöyle bir soru gelebilir; ICustomerManager ve ICategoryManager arayüzlerinin içerisinde ekstradan herhangi bir metot bildirimi yapılmadı, o halde neden tanımlama gereği duyuldu, neden IRepository arayüzü direkt Manager sınıflara implement edilmedi ?

IRepository<T> arayüzü içerisinde tüm Manager sınıflarda bulunması gereken metotların bildirimi yapılır. Ancak Manager sınıfların içerisinde IRepository<T> arayüzünde bildirilmiş metotların haricinde kendilerine has metotlar da bulunabilir. Örneğin CustomerManager sınıfı içerisinde müşterilerin adres bilgisini döndüren bir metot olabilir. Eğer IRepository<T> arayüzü içerisinde bu metodun bildirimini yapmış olsaydık, bu metodu CategoryManager içerisinde de gerek olmamasına rağmen tanımlamamız gerekirdi. Bu yüzden tüm Manager sınıflar için ortak olan metotlar IRepository<T> arayüzünde bildirilir, Manager sınıfa özel olan metotlarda o Manager sınıfa özel olarak oluşturulmuş arayüzde bildirilir.

Son olarak; dikkat edecek olursak, IRepository<T> arayüzüne herhangi bir kısıt konulmamış durumda. Bu yüzden arayüze veri tipi olarak hem değer tipliler hem de referans tipliler gönderilebilir. Ancak biz arayüze sadece Customer ve Category gibi Entity’lerin gönderilmesini istiyoruz. O halde IRepository<T> arayüzüne bir kısıt eklememiz gerekiyor.

public interface IEntity { }

public interface IRepository<T> where T : class, IEntity
{
    List<T> List();
    T Find(int id);
    bool Remove(T entity);
}

IEntity adında bir arayüz oluşturduk ve where T : class, IEntity kısıtı ile IRepository<T> arayüzüne sadece IEntity arayüzü implement alan referans tiplilerin gönderilmesine izin verdik.  IRepository<T> arayüzüne Customer ve Category sınıflarının gönderilmesini istediğimizden IEntity arayüzünü bu sınıflara implement ediyoruz. Kod bloklarının son hali;
public interface IEntity { }
public interface IRepository<T> where T : class, IEntity
{
    List<T> List();
    T Find(int id);
    bool Remove(T entity);
}

public class Customer : IEntity
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public string Address { get; set; }
}
public class Category : IEntity
{
    public int Id { get; set; }
    public string Title { get; set; }
}

public interface ICustomerManager : IRepository<Customer> { }
public interface ICategoryManager : IRepository<Category> { }

public class CustomerManager : ICustomerManager
{
    private static List<Customer> _customers = new List<Customer>() {
        new Customer { Id = 1, FullName = "Serdar YILMAZ", Address = "Kocaeli" },
        new Customer { Id = 2, FullName = "Behzat GÖK", Address = "Sakarya" },
        new Customer { Id = 3, FullName = "Sinem ER", Address = "Bolu" }
    };

    public List<Customer> List() { return _customers; }
    public Customer Find(int id) { return _customers.Find(x => x.Id == id); }
    public bool Remove(Customer customer) { return _customers.Remove(customer); }
}

public class CategoryManager : ICategoryManager
{
    private static List<Category> _category = new List<Category>() {
         new Category { Id = 1, Title="Yazılım"},
         new Category { Id = 2, Title="Network"},
    };

    public List<Category> List() { return _category; }
    public Category Find(int id) { return _category.Find(x => x.Id == id); }
    public bool Remove(Category category) { return _category.Remove(category); }
}

Serdar YILMAZ

C# – Generic Kısıtlar

.net

Bir önceki yazımızda(*) Generic sınıfların, metotların ve arayüzlerin nasıl oluşturulduğundan bahsettik. Bu yazımızda ise Generic sınıflara, metotlara ve arayüzlere ne tür kısıtlamalar getirebileceğimizden bahsedeceğiz.

Değer ve Referans Tip Kısıtı

public class ExampleClass<T>
{
    public T example_1 { get; set; }
    public T example_2(T parameter)
    {
        return parameter;
    }
}

ExampleClass Generic sınıfına herhangi bir kısıt uygulanmadığı için T yerine int, double, float gibi değer tipleri gönderebileceği gibi; string, object, array gibi referans tipleri de gönderilebilir.

Değer Tipleri: “int”, “long”, “float”, “double”, “decimal”, “char”, “bool”, “byte”, “short”, “struct”, “enum”
Referans Tipleri: “string”, “object”, “class”, “interface”, “array”, “delegate”, “pointer”

Değer ve referans tipler hakkında daha detaylı bilgi edinmek için Değer ve Referans Parametreleri başlıklı içeriği okuyabilirsiniz.

class Program
{
    static void Main(string[] args)
    {
        ExampleClass<int> obj1 = new ExampleClass<int>();
        obj1.example_1 = 18;
        obj1.example_2(128);

        ExampleClass<string> obj2 = new ExampleClass<string>();
        obj2.example_1 = "Serdar YILMAZ";
        obj2.example_2("C# Doküman");
    }
}

Oluşturduğumuz ExampleClass Generic sınıfına sadece referans tip veya sadece değer tip gönderilmesini isteyebiliriz. Böylesi bir durumda Generic sınıfımıza bir kısıt koymamız gerekmekte.

Sadece referans tip gönderilmesi için;  

public class ExampleClass<T> where T:class
{
    public T example_1 { get; set; }
    public T example_2(T parameter)
    {
        return parameter;
    }
}

ExampleClass Generic sınıfına, “where T:class” kısıtı sayesinde sadece referans tipleri gönderilebilir. Değer tipli bir tür gönderildiği takdirde hata oluşacak ve proje derlenmeyecektir.
ExampleClass<string> obj1 = new ExampleClass<string>(); // Geçerli tip
ExampleClass<object> obj2 = new ExampleClass<object>(); // Geçerli tip

ExampleClass<int> obj3 = new ExampleClass<int>();       // HATA! Geçersiz tip
ExampleClass<bool> obj4 = new ExampleClass<bool>();     // HATA! Geçersiz tip

Sadece değer tip gönderilmesi için; 
public class ExampleClass<T> where T : struct
{
    public T example_1 { get; set; }
    public T example_2(T parameter)
    {
        return parameter;
    }
}

ExampleClass Generic sınıfına, “where T : struct” kısıtı sayesinde sadece değer tipleri gönderilebilir. Referans tipli bir tür gönderildiği takdirde hata oluşacak ve proje derlenmeyecektir.
ExampleClass<int> obj1 = new ExampleClass<int>();        // Geçerli tip
ExampleClass<bool> obj2 = new ExampleClass<bool>();      // Geçerli tip

ExampleClass<string> obj3 = new ExampleClass<string>();  // HATA! Geçersiz tip
ExampleClass<object> obj4 = new ExampleClass<object>();  // HATA! Geçersiz tip

new() Kısıtı

Generic sınıfa gönderilen veri tipini, sınıf içerisinde new’liyorsak, yani o tipten yeni bir nesne oluşturuyorsak; Generic sınıfa “new() kısıtını uygulamamız gerekmektedir. Eğer Visual Studio ile geliştirme yapıyorsanız zaten IDE sizi new() kısıtını uygulamaya zorlayacaktır.

public class ExampleClass<T> where T : new()
{
    public T createObject()
    {
        T obj = new T();

        return obj;
    }
}

ExampleClass Generic sınıfında gönderilen veri tipi createObject metodunda new’lenerek yeni bir nesne oluşturulmak istendiğinden new() kısıtı uygulanmıştır. Artık ExampleClass Generic sınıfına sadece nesne oluşturulabilen yani new() lenebilen türler gönderilebilir.

Arayüz kısıtı

Generic sınıfa sadece belirttiğimiz arayüzleri implement alan türlerin gönderilmesini istiyorsak arayüz kısıtını uygularız.

public interface IExample1 { }
public interface IExample2 { }

public class ExampleClass1 : IExample1
{
    public string example_1 { get; set; }
}
public class ExampleClass2 : IExample1
{
    public string example_1 { get; set; }
}
public class ExampleClass3 : IExample2
{
    public string example_1 { get; set; }
}

IExample1 ve IExample2 isminde iki arayüz oluşturduk. IExample1 arayüzünü ExampleClass1 ve ExampleClass2‘ye implement ettik. IExample2 arayüzünü de ExampleClass3 sınıfına implement ettik.
public class GenericClass<T> where T : IExample1
{
    public T example_1 { get; set; }
    public T example_2(T parameter) { return parameter; }
}

Generic sınıfımıza “where T : IExample1” kısıtını eklediğimiz takdirde; Generic sınıfımıza veri türü olarak sadece IExample1 arayüzünü implement alan ExampleClass1 ve ExampleClass2 gönderilebilecektir. ExampleClass3 sınıfı IExample1 arayüzünü implement almadığı için GenericClass’a veri tipi olarak gönderilmek istendiğinde hata alınacaktır. Arayüz kısıtı sayesinde Generic sınıflara sadece belli arayüzleri implement alan sınıfların gönderilmesini sağlayabiliriz.

Birden Fazla Kısıt Ekleme

Yukarıda anlatmış olduğumuz kısıtları birlikte de kullanabiliriz.

public interface IExample1 { }

public class ExampleClass<T> where T : class, IExample1 , new()
{
    public T createObject()
    {
        T obj = new T();

        return obj;
    }
}

ExampleClass Generic sınıfına sadece referans tipli olan, IExample1 arayüzünü implement alan ve new’lenebilen veri türleri gönderilebilir.

new() kısıtı her zaman en son da yer almalıdır.

Generic Metotlara Kısıtların Eklenmesi

Generic kısıtları tıpkı sınıflarda kullandığımız gibi generic metotlarda da kullanabiliriz.

public class ExampleClass
{
    public T createObject<T>() where T : class, IExample1, new()
    {
        T obj = new T();

        return obj;
    }
}

createObject Generic metoduna sadece referans tipli olan, IExample1 arayüzünü implement alan ve new’lenebilen veri türleri gönderilebilir.

Serdar YILMAZ

C# – Generic Sınıflar, Metotlar ve Arayüzler

.net
Bu içerikte Generic’leri anlatırken, konuya aşinalığı sağlamak adına basit örnekler üzerinden, özellikle yazım kurallarını ön planda tutarak içeriği oluşturacağım. Sonradan yayınlayacağım içeriklerde ise Generic’lere daha detaylı bir şekilde değinip, gerçek hayatta kullanımına dair örneklere yer vereceğim.

Generic Sınıflar 

Şu ana kadar oluşturduğumuz sınıflarda, sınıf içerisindeki değişkenlerin, metotların ve parametrelerin veri tiplerini onları tanımlarken belirttik.

public class Example
{
    public int example_1;
    public void example_2(int parameter) { }
}

Example sınıfında example_1 değişkeninin veri tipini ve example_2 metodunun parametresinin veri tipini de int olarak belirttik. Artık example_1 değişkenine int haricinde bir değer atamamız veya example_2 metoduna int’den farklı bir parametre göndermemiz mümkün değildir.

Generic yapıyı kullandığımız takdirde sınıf içerisindeki değişkenlerin, parametrelerin ve metotların geri dönüş tiplerini o sınıftan bir nesne oluştururken belirleyebilmekteyiz.

public class Example<T>
{
    public T example_1;
    public void example_2(T parameter) { }
}

Example sınıfından nesne oluştururken T (T yerine farklı bir isim verilebilir, isteğe bağlı) yerine hangi veri tipini yazarsak; sınıf içerisindeki değişken ve parametrenin tipi o şekilde olacaktır.
class Program
{
    static void Main(string[] args)
    {
        Example<int> exp_1 = new Example<int>();
        exp_1.example_1 = 18;
        exp_1.example_2(128);

        Example<string> exp_2 = new Example<string>();
        exp_2.example_1 = "Serdar YILMAZ";
        exp_2.example_2("C# - Generic Sınıflar");
    }
}

exp_1 nesnesinde example_1 değişkeni ve example_2 metodunun parametresi int tipinde olacaktır. exp_2 nesnesinde ise example_1 değişkeni ve example_2 metodunun parametresi string tipinde olacaktır. Yani Generic’ler sayesinde bir sınıfın elemanlarının veri tiplerini ihtiyaç doğrultusunda yeni bir sınıf tanımlamaya gerek kalmadan değiştirebilmekteyiz.

generic c#

Generic Metotlar

Bazen bir sınıf içerisindeki metotların sadece birkaçını Generic olarak kullanmak isteyebiliriz. Böylesi bir durumda sınıfı Generic yapmak yerine sadece ilgili metotları Generic yapmak çok daha mantıklı olacaktır.

public class Example
{
    public int example_1(int parameter)
    {
        return parameter;
    }
    public bool example_2(bool parameter)
    {
        return parameter;
    }
    public T example_3<T>(T parameter)
    {
        return parameter;
    }
    public string example_4<T>(T parameter)
    {
        return parameter.ToString();
    }
}

example_1 ve example_2 metotlarının parametre ve geri dönüş tipleri bellidir, bu yüzden farklı türden bir değer almaları veya geriye döndürmeleri mümkün değildir. Ancak example_3 metodu Generic olarak tanımlandığı için parametre ve geri dönüş tipi çağrılmadan önce belirtilmelidir. example_4 metodunun ise geri dönüş tipi string olup, parametresinin veri tipi çağrılmadan önce belirtilmelidir.
 class Program
 {
     static void Main(string[] args)
     {
         Example exp_1 = new Example();
         Console.WriteLine(exp_1.example_1(18));
         Console.WriteLine(exp_1.example_2(true));

         // Metodun parametre ve geri dönüş tipi string olacaktır.
         Console.WriteLine(exp_1.example_3<string>("Serdar YILMAZ"));
         // Metodun parametre ve geri dönüş tipi double olacaktır.
         Console.WriteLine(exp_1.example_3<double>(3.14));

         // Metot int tipinde parametre alacaktır.
         Console.WriteLine(exp_1.example_4<int>(15));
         // Metot string tipinde parametre alacaktır.
         Console.WriteLine(exp_1.example_4<string>("Serdar YILMAZ"));
    }
}

Ekran Çıktısı:
18
True
Serdar YILMAZ
3,14
15
Serdar YILMAZ
Press any key to continue . . .

Generic Arayüzler

Tıpkı sınıflarda olduğu gibi arayüzleri de Generic olarak tanımlayabiliriz.

public interface IExample<T>
{
    T example_1(T input);
    void example_2(T input1, T input2);
}

Tek fark; Generic sınıflarda veri tipini o sınıftan bir nesne oluştururken belirtmekteyiz, Generic arayüzlerde ise veri tipini, o arayüzü bir sınıfa implement ederken belirtmekteyiz.
public class ExampleClass_1 : IExample<int>
{
    public int example_1(int input) { return input; }

    public void example_2(int input1, int input2) { }
}

public class ExampleClass_2 : IExample<string>
{
    public string example_1(string input) { return input; }

    public void example_2(string input1, string input2) { }
}

Arayüzlerde Generic Metot Bildirimi 

Arayüz içerisindeki metotların tamamı Generic değilse; Arayüz yerine sadece ilgili metotları Generic yapmak daha mantıklı olacaktır, tıpkı Generic metotlarda anlattığımız gibi.

public interface IExample
{
    void example_1(int input);
    T example_2<T>(T input);
}

public class ExampleClass_1 : IExample
{
    public void example_1(int input) { }

    public T example_2<T>(T input) { return input; }
}

IExample arayüzü Generic olmadığı için herhangi veri tipi belirtmeden ExampleClass_1 sınıfına implement edebildik. example_2 metodu Generic olduğu için veri tipi metot çağrılırken girilecektir.
class Program
{
    static void Main(string[] args)
    {
        ExampleClass_1 example = new ExampleClass_1();
        example.example_1(18);
        example.example_2<string>("Serdar YILMAZ");
    }
}

Statik Generic Sınıflar

Statik sınıflar ile statik olmayan sınıfların Generic yapılması noktasında arada herhangi bir fark bulunmuyor. Statik sınıfların metot ve değişkenlerine nesne oluşturmadan erişebildiğimiz için, veri tipini sınıf adını yazdıktan hemen sonra belirtiyoruz.

public static class Example<T>
{
    public static T example_1(T input) { return input; }
}

class Program
{
    static void Main(string[] args)
    {
        Example<string>.example_1("Serdar YILMAZ");
    }
}

Generic’lerde Birden Fazla Veri Tipinin Kullanılması

Generic ifadelere birden fazla veri tipi gönderebiliriz.

public class Example<T, Y, C>
{
    public T example_1;
    public void example_2(Y parameter1, C parameter2) { }
}

class Program
{
    static void Main(string[] args)
    {
        Example<int, string, bool> example = new Example<int, string, bool>();
        example.example_1 = 18;
        example.example_2("Serdar YILMAZ", true);
    }
}

Serdar YILMAZ

C# – Merkezi İstisnai Durum Yönetimi

.netTry-Catch-Finally blokları ile çalışma anında meydana gelen hataları nasıl yakalayabileceğimizi ve türlerine göre nasıl filtreleyebileceğimizi öğrendik. Hataların türlerine göre filtrelenmesi işleminde bazen Catch bloklarının sayısı bir hayli fazla olabilmekte.

public class ExampleClass
{
    public void ExampleMethod_1()
    {
        try
        {
            // Metodun Görevi
        }
        catch (ArgumentNullException ex)
        {
        }
        catch (IndexOutOfRangeException ex)
        {
        }
        catch (FormatException ex)
        {
        }
        catch (OverflowException ex)
        {
        }
        catch (Exception ex)
        {
        }
    }

    public void ExampleMethod_2()
    {
        try
        {
            // Metodun Görevi
        }
        catch (ArgumentNullException ex)
        {
        }
        catch (IndexOutOfRangeException ex)
        {
        }
        catch (FormatException ex)
        {
        }
        catch (OverflowException ex)
        {
        }
        catch (Exception ex)
        {
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        ExampleClass exampleClass = new ExampleClass();
        exampleClass.ExampleMethod_1();
        exampleClass.ExampleMethod_2();
    }
}

ExampleClass sınıfının içerisinde tanımlanmış olan ExampleMethod_1 ve ExampleMethod_2 metotlarını inceleyecek olursak; Catch bloklarından dolayı metotların gövdesinde kod kalabalığı oluştuğunu görebiliriz. Üstelik henüz metodun görevini ve Catch bloklarının içeriğini yazmadık bile! ExampleMethod_1 ve ExampleMethod_2 gibi içerinde bir çok Catch bloğu barındıran, onlarca metodun oluşturduğu bir sınıftaki kod kalabalığını tahmin edebilirsiniz.

Tüm metotlarda Try-Catch-Finally bloklarını tekrar tekrar yazmak yerine, oluşan hataları yakalayan ve türlerine göre filtreleyen merkezi bir İstisnai Durum Yönetimi metodu oluşturarak, kod kalabalığını ciddi anlamda azaltabiliriz. Bunun için öncelikle yukarıdaki ExampleClass sınıfımızı Try-Catch bloklarından arındırıp, geriye sadece metotların görevlerini yani metotlar çağrıldığında çalışmasını istediğimiz kodları bırakıyoruz.

public class ExampleClass
{
    public void ExampleMethod_1()
    {
        // Metodun Görevi
    }

    public void ExampleMethod_2()
    {
        // Metodun Görevi
    }
}

ExampleClass sınıfındaki Try-Catch bloklarını kaldırarak kod kalabalığını ciddi anlamda azalttık. Şimdi, ExampleMethod_1 ve ExampleMethod_2 metotlarının çalışması esnasında ortaya çıkan hataları yakalayan ve türlerine göre filtreleyen metodumuzu yazıyoruz.
static void ExceptionCatcher(Action action)
{
    try
    {
        action.Invoke();
    }
    catch (ArgumentNullException ex)
    {
    }
    catch (IndexOutOfRangeException ex)
    {
    }
    catch (FormatException ex)
    {
    }
    catch (OverflowException ex)
    {
    }
    catch (Exception ex)
    {
    }
}

Oluşturduğumuz ExceptionCatcher metodunu dikkatlice inceleyecek olursak; Action türünde bir parametre aldığını görebiliriz. Bu da şu anlama geliyor; biz ExceptionCatcher metoduna parametre olarak bir metot veya kod bloğu gönderebiliriz (Delegate’leri anlatacağım içeriklerde bu konuya daha detaylı bir şekilde değineceğim).

ExceptionCatcher metoduna parametre olarak gönderdiğimiz metotları Invoke() ile çağırmaktayız. Yani toparlayacak olursak; ExceptionCatcher metoduna parametre olarak ExampleMethod_1 ve ExampleMethod_2 metotlarını göndereceğiz ve Invoke() metodu ile bu metotları Try bloğu içerisinde çalıştırıp, hata oluşması durumunda da Catch blokları ile filtreleyeceğiz.

public class ExampleClass
{
    public void ExampleMethod_1()
    {
        // Metodun Gövdesi
    }

    public void ExampleMethod_2()
    {
        // Metodun Gövdesi
    }
}

class Program
{
    static void Main(string[] args)
    {
        ExampleClass exampleClass = new ExampleClass();

        ExceptionCatcher(() => 
        {
            exampleClass.ExampleMethod_1();
        });

        ExceptionCatcher(() => 
        {
            exampleClass.ExampleMethod_2();
        });
    }

    static void ExceptionCatcher(Action action)
    {
        try
        {
            action.Invoke();
        }
        catch (ArgumentNullException ex)
        {
        }
        catch (IndexOutOfRangeException ex)
        {
        }
        catch (FormatException ex)
        {
        }
        catch (OverflowException ex)
        {
        }
        catch (Exception ex)
        {
        }
    }
}

Son olarak ExceptionCatcher metoduna parametre olarak bir metot göndermek yerine, bir kod bloğu göndereceğimiz farklı bir örnek yapalım.
class Program
{
    static void Main(string[] args)
    {
        ExceptionCatcher(() => 
        {
            Console.Write("Birinci sayıyı giriniz: ");
            int sayi_1 = Convert.ToInt32(Console.ReadLine());

            Console.Write("İkinci sayıyı giriniz: ");
            int sayi_2 = Convert.ToInt32(Console.ReadLine());

            double sonuc = sayi_1 / sayi_2;
            Console.WriteLine("Birinci sayının ikinci sayıya bölümü:{0}", sonuc);
        });          
    }

    static void ExceptionCatcher(Action action)
    {
        try
        {
            action.Invoke();
        }
        catch (FormatException exception)
        {
            // Klavyeden sayı yerine harf veya karakter girildiğinde bu catch bloğu çalışacak.
            Console.WriteLine("Hata! Sadece sayı girilebilir. Hata Mesajı:{0}", exception.Message);
        }
        catch (DivideByZeroException exception)
        {
            // sayi_2 değişkenine sıfır değeri atandığında bu catch bloğu çalışacak.
            Console.WriteLine("Hata! Bir sayının sıfıra bölümü sonsuzdur. Hata Mesajı:{0}", exception.Message);
        }
        catch (Exception exception)
        {
            // Öngördüğümüz hatalar haricinde bir hata oluştuğunda bu catch bloğu çalışacak.
            Console.WriteLine("Beklenmedik bir hata oluştu. Hata Mesajı:{0}", exception.Message);
        }
    }
}

Yukarıdaki uygulamamızda ExceptionCatcher metoduna parametre olarak bölme işlemini gerçekleştiren kod bloğunu gönderdik. Göndermiş olduğumuz kod bloğu ExceptionCatcher metodunun Try bloğunda çalışacaktır. Klavyeden sayı yerine harf veya karakter girildiğinde veya sayi_2 değişkenine sıfır değeri atandığında oluşacak hatalar ilgili Catch bloğu tarafından yakalanacaktır.

Serdar YILMAZ

C# – Exception Sınıfı Oluşturma

.netİstisnai Durum Yönetimi başlıklı yazımızda Try-Catch-Finaly blokları ile uygulamamızda meydana gelen hataları nasıl yakalayacağımıza ve türlerine göre nasıl filtreleyebileceğimize değinmiştik. Bu yazımızda ise; kendi Exception sınıflarımızı nasıl oluşturacağımıza ve hangi amaçlar doğrultusunda kullanabileceğimize değineceğiz.

Hazır Exception Sınıfları

.NET Framework içerisinde bir çok hazır Exception sınıfı bulunmaktadır. Uygulamamızda çalışma anında bir hata meydana geldiğinde .Net,  hatanın türüne göre ilgili Exception sınıfından bir nesne oluşturarak geriye fırlatmaktadır.

class Program
{
    static void Main(string[] args)
    {
        int sayi_1 = 18;
        int sayi_2 = 0;

        try
        {
            int sonuc = sayi_1 / sayi_2;
        }
        catch(Exception ex)
        {
            Console.WriteLine("Hata Mesajı:{0}", ex.Message);
        }
    }
}

Yukarıdaki örneği inceleyecek olursak; Bir sayının sıfıra bölümü sonsuz olduğundan, sayi_1 değişkeni sayi_2 değişkenine bölünmek istendiğinde .Net bir Exception nesnesi fırlatacaktır. Bize düşen ise try-catch blokları ile bu hatayı yakalamaktır. Yukarıdaki uygulama çalıştırıldığı takdirde ekran çıktısı aşağıdaki gibi olacaktır;
Hata Mesajı:Sıfırla bölme girişiminde bulunuldu.
Press any key to continue . . .

Ekran çıktısını inceleyecek olursak; .Net’in sadece hata vermekle kalmadığını, hatanın neden oluştuğuna dair bilgide verdiğini görebiliriz. Tıpkı bu örnekte olduğu gibi, bizlerde hatalı işlem yapılmasını önlemek amacıyla kontrollü bir şekilde Exception’lar fırlatabiliriz.

Kendi Exception Sınıflarımızı Nasıl Oluşturabiliriz ?

Exception sınıflarının nasıl oluşturulduğunu ve kullanıldığını örnek bir senaryo üzerinden ilerleyerek anlamaya çalışalım. Kullanıcıların şifrelerini değiştirebileceği bir metot yazmak istediğimizde, metot içerisinde en basit haliyle aşağıdaki kontrolleri yapabiliriz;

  1. Belirtilen kullanıcı adı sistemimizde var mı? Eğer yoksa olmayan bir kullanıcının şifresi değiştirilmek isteniyor demektir, böylesi bir durumda bir hata mesajı fırlatmamız gerekir.
  2. Şifresini değiştirmek isteyen kullanıcıdan, doğrulama amaçlı eski şifresini girmesini istediğimizde yanlış bir şifre girerse bir hata mesajı döndürmemiz gerekir.

Yukarıdaki iki durumu da dikkate alarak Exception sınıflarımızı oluşturalım;

class UserNotFoundException : Exception
{
    public UserNotFoundException() : base("Kullanıcı Adı Hatalı!") { }
}

class WrongPasswordException : Exception
{
    public WrongPasswordException() : base("Hatalı Şifre!") { }
}

Kullanıcı sistemimize kayıtlı olmadığı durumda fırlatacağımız UserNotFoundException ve yanlış şifre girildiği zaman fırlatacağımız WrongPasswordException sınıflarımızı oluşturduk. Dikkat edilecek olursa her iki sınıfımızda Exception sınıfından türetilmiştir ve base anahtar sözcüğü ile hata mesajları temel sınıf olan Exception sınıfının yapıcı metoduna parametre olarak gönderilmiştir.

Oluşturacağımız Exception sınıfları, “Exception” sınıfından türetilmelidir.

base anahtar sözcüğünün kullanımı hakkında daha detaylı bilgi edinmek için Yapıcı Metotlar ve Kalıtım başlıklı içeriği okuyabilirsiniz.

Nasıl Exception Fırlatabiliriz ? 

Exception sınıflarımızı oluşturduğumuza göre şimdi onları hatalı işlem yapılmasını önlemek amacıyla kontrollü bir şekilde fırlatabiliriz. Örnek senaryo üzerinden giderek sınıfımızı ve metodumuzu oluşturalım;

class UserManager
{
    private static Dictionary<string, string> _users = new Dictionary<string, string>
    {
        {"srdrylmz","123456789"},
        {"wordpress", "798456132"}
    };

    public void PasswordChange(string userName, string oldPassword, string newPassword)
    {
        if (_users.ContainsKey(userName))
        {
            if (_users[userName] != oldPassword)
                throw new WrongPasswordException();

            _users[userName] = newPassword;
        }
        else
        {
            throw new UserNotFoundException();
        }
    }
}

UserManager isimli class içerisinde kod kalabalığını arttırmamak için; kullanıcı adı ve şifre çiftlerini tutan bir Dictionary tanımlayıp, kullanıcının kayıt olup-olmadığını, şifrenin doğru girilip-girilmediğini bu koleksiyon sınıfı üzerinden kontrol ettik. Sizler bu işlemleri veritabanı üzerinden yapabilirsiniz. Dictionary hakkında daha detaylı bilgi edinmek için Dictionary Sınıfı başlıklı içeriği okuyabilirsiniz.

PasswordChange() isimli metodumuzu açıklayacak olursak; İlk olarak parametre olarak gelen kullanıcı adının _Users koleksiyonunda olup olmadığını kontrol ediliyor ve eğer kullanıcı mevcut değilse throw anahtar sözcüğü ile UserNotFoundException istisnası fırlatılıyor. Kullanıcı mevcut ise parametre olarak gelen şifre bilgisinin doğru olup olmadığı kontrol ediliyor ve şifre bilgisi yanlış ise WrongPasswordException istisnası fırlatılıyor. Doğru girildiği takdirde de ilgili kullanıcının şifre bilgisini güncelleniyor.

throw anahtar sözcüğü ile oluşturduğumuz exception sınıflarını nasıl fırlatacağımızı öğrendik, şimdi sıra onları yakalamakta.

UserManager userManager = new UserManager();

try
{
    userManager.PasswordChange("serdaryilmaz", "123465", "6543210");
}
catch(Exception ex)
{
    Console.WriteLine(ex.Message);
}

_Users koleksiyonunda “serdaryilmaz” adında bir kullanıcı olmadığı için UserNotFoundException istisnasını fırlatacaktır. Ekran çıktısı;
Kullanıcı Adı Hatalı!
Press any key to continue . . .

UserManager userManager = new UserManager();

try
{
    userManager.PasswordChange("srdrylmz", "123465", "6543210");
}
catch(Exception ex)
{
    Console.WriteLine(ex.Message);
}

“srdrylmz” kullanıcısının şifresi yanlış girildiğinden WrongPasswordException istisnasını fırlatacaktır.
Şifre Hatalı!
Press any key to continue . . .

Serdar YILMAZ

C# – İstisnai Durum Yönetimi

.netUygulamalarımızı geliştirirken bir syntax hatası (Kod satırlarının sonuna noktalı virgül koymayı unutmak gibi) yaptığımızda, Visual Studio gerekli uyarıyı vererek projeyi derlememizi engelleyecektir. Ancak çalışma anında ortaya çıkabilecek hataları Visual Studio’nun önceden tespit edebilmesi mümkün değildir. Bu yüzden çalışma anında ortaya çıkabilecek hataların uygulamamızı çökertmesine izin vermemek için Try-Catch-Finally ile istisnai durum yönetimine başvururuz.

class Program
{
    static void Main(string[] args)
    {
        Console.Write("Birinci sayıyı giriniz: ");
        int sayi_1 = Convert.ToInt32(Console.ReadLine());

        Console.Write("İkinci sayıyı giriniz: ");
        int sayi_2 = Convert.ToInt32(Console.ReadLine());

        double sonuc = sayi_1 / sayi_2;
        Console.WriteLine("Birinci sayının ikinci sayıya bölümü:{0}", sonuc);
    }
}

Yukarıdaki uygulama Visual Studio tarafından sorunsuz bir şekilde derlenecektir ve doğru değerler girildiği sürece program sorunsuz bir şekilde çalışacaktır.  Ancak programı kullanan kullanıcı klavyeden sayı yerine harf veya karakter girdiğinde veya sayi_2 değişkenine sıfır değerini atadığında (bir sayının sıfıra bölümü sonsuzdur) uygulamamız çökecektir.

Çalışma anında ortaya çıkan hataların uygulamamızı çökertmemesi için Try-Catch-Finally bloklarını kullanırız.

Try-Catch-Finally Blokları

Try Bloğu: Çalışma anında hata çıkarma olasılığı olan kodlarımızı Try bloğu içerisine yazarız. Eğer Try bloğu içerisine yazılmış olan kodlarda bir hata meydana gelirse, oluşan hata bir Exception nesnesi olarak catch bloğuna gönderilir.

Catch Bloğu: Try bloğu içerisine yazılmış olan kodlarda bir hata meydana geldiği an, program Try bloğundan çıkarak Catch bloğuna girer. Uygulamamız çalışırken hata oluşması durumunda, uygulanmasını istediğimiz çözüm senaryosunu Catch bloğu içerisine yazarız.

Finally Bloğu: Try bloğu içerisinde bir hata meydana gelmediği sürece Catch bloğu içerisindeki kodlar çalışmaz. Ancak Finally Bloğu hata meydana gelse de, gelmese de her halükarda çalışır. Finally opsiyonel bir bloktur, istenilmediği taktirde yazılmayabilir.

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Console.Write("Birinci sayıyı giriniz: ");
            int sayi_1 = Convert.ToInt32(Console.ReadLine());

            Console.Write("İkinci sayıyı giriniz: ");
            int sayi_2 = Convert.ToInt32(Console.ReadLine());

            double sonuc = sayi_1 / sayi_2;
            Console.WriteLine("Birinci sayının ikinci sayıya bölümü:{0}", sonuc);
        }
        catch(Exception e)
        {
            Console.WriteLine(e.Message);
        }
        finally
        {
            // finally bloğu opsiyoneldir, yazılmayabilir. 
        }
    }
}

Artık sayı yerine harf girildiğinde veya sayi_2 değişkenine sıfır değeri atandığında uygulamamız çökmeyecektir. Hata meydana geldiği an arkaplanda bir Exception nesnesi oluşturulup Catch bloguna parametre olarak gönderilecektir. Bu parametre aracılığıyla oluşan hata hakkında bilgi alınabilir.


Yukarıdaki örnekte Exception’da ki hata mesajı kullanıcıya gösterilmek üzere ekrana yazdırıldı ancak profesyonelce geliştirilen projelerde Exception’da ki hata mesajı doğrudan kullanıcıya sunulmaz. Bunun başlıca iki sebebi bulunmaktadır.

  1. Exception’da ki hata mesajı genellikle kullanıcıların anlayamayacağı teknik terimleri içerir.
  2. Exception’da ki hata mesajı uygulamamız hakkında bilinmemesi gereken bilgileri içeriyor olabilir.

Bu yüzden Exception’da ki hata mesajını genellikle loglamak amacıyla kullanırız.

Oluşan Hataları Filtreleme

Try bloğu içerisine yazdığımız kodlarda farklı türden hatalar meydana gelebilir. Eğer her hata türü için farklı bir çözüm senaryomuz varsa, oluşan hataları filtrelememiz gerekir. Yukarıdaki örneğimizde iki farklı hata söz konusuydu; ilki klavyeden sayı yerine harf veya karakter girildiğinde ortaya çıkarken, ikincisi sayi_2 değişkenine sıfır değeri atandığında ortaya çıkmaktaydı. Oluşan hataları filtreleyecek şekilde uygulamamızı yeniden yazacak olursak;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Console.Write("Birinci sayıyı giriniz: ");
            int sayi_1 = Convert.ToInt32(Console.ReadLine());

            Console.Write("İkinci sayıyı giriniz: ");
            int sayi_2 = Convert.ToInt32(Console.ReadLine());

            double sonuc = sayi_1 / sayi_2;
            Console.WriteLine("Birinci sayının ikinci sayıya bölümü:{0}", sonuc);
        }
        catch (FormatException exception)
        {
            // Klavyeden sayı yerine harf veya karakter girildiğinde bu catch bloğu çalışacak.
            Console.WriteLine("Hata! Sadece sayı girilebilir. Hata Mesajı:{0}", exception.Message);
        }
        catch (DivideByZeroException exception)
        {
            // sayi_2 değişkenine sıfır değeri atandığında bu catch bloğu çalışacak.
            Console.WriteLine("Hata! Bir sayının sıfıra bölümü sonsuzdur. Hata Mesajı:{0}", exception.Message);
        }
        catch (Exception exception)
        {
            // Öngördüğümüz hatalar haricinde bir hata oluştuğunda bu catch bloğu çalışacak.
            Console.WriteLine("Beklenmedik bir hata oluştu. Hata Mesajı:{0}", exception.Message);
        }
    }
}

“FormatException” ve “DivideByZeroException” gibi bir çok hazır Exception sınıfı bulunmakta. Try bloğunda hata meydana geldiği an arkaplanda bir Exception nesnesi oluşturulur ve Catch blokları yukarıdan aşağıya incelenir, oluşan hata türü hangi Catch bloğuna uyuyorsa o Catch bloğu çalışır. Klavyeden sayı yerine harf veya karakter girildiğinde ilk Catch bloğu çalışacaktır, sayi_2 değişkenine sıfır değeri atandığında da ikinci Catch bloğu çalışacaktır. “FormatException” ve “DivideByZeroException” haricinde bir hata meydana geldiğinde de üçüncü Catch bloğu çalışacaktır.

“catch(Exception exception)” şeklinde tanımlanan Catch bloğu tüm hata türlerini kapsar. O yüzden hata türüne göre filtreleme işlemi yaparken “catch(Exception exception)” bloğunu en sona yazmalıyız.

try
{
    Console.Write("Birinci sayıyı giriniz: ");
    int sayi_1 = Convert.ToInt32(Console.ReadLine());

    Console.Write("İkinci sayıyı giriniz: ");
    int sayi_2 = Convert.ToInt32(Console.ReadLine());

    double sonuc = sayi_1 / sayi_2;
    Console.WriteLine("Birinci sayının ikinci sayıya bölümü:{0}", sonuc);
}
catch (Exception exception)
{
    Console.WriteLine("Beklenmedik bir hata oluştu. Hata Mesajı:{0}", exception.Message);
}
catch (FormatException exception)
{
    Console.WriteLine("Hata! Sadece sayı girilebilir. Hata Mesajı:{0}", exception.Message);
}
catch (DivideByZeroException exception)
{
    Console.WriteLine("Hata! Bir sayının sıfıra bölümü sonsuzdur. Hata Mesajı:{0}", exception.Message);
}

Eğer “catch (Exception exception)” bloğunu en üste yazarsak; Catch blokları yukarıdan aşağıya doğru incelendiği için ve “catch (Exception exception)” bloğu da tüm hata türlerini kapsadığı için;  “catch (FormatException exception)” ve “catch (DivideByZeroException exception)” blokları hiç bir zaman çalışmayacaktır. Tüm hata türleri “catch (Exception exception)” bloğu tarafından yakalanacaktır.

Serdar YILMAZ

C# – Abstract Sınıflar

.netTamamen kalıtım amaçlı kullanacağımız temel sınıfları oluştururken Abstract anahtar sözcüğünü kullanırız. Abstract bir sınıf oluşturabilmek için erişim belirtecinden sonra “abstract” anahtar sözcüğünü yazmamız gerekmektedir. Abstract sınıflar içerisinde hem metot tanımlayabilir hem de arayüzler de olduğu gibi metot bildirimi yapabiliriz.

public abstract class TemelSinif
{
    public void Metot_1()
    {
        Console.WriteLine("Abstract sınıf içerisinde tanımlanmış metot.");
    }

    public abstract void Metot_2();
}

TemelSinif, abstract bir sınıf olduğu için sadece kalıtım amaçlı kullanılabilir. Metot_1(), TemelSinif‘dan türetilen sınıflara doğrudan aktarılacaktır. Bildirimi yapılmış olan Metot_2()‘nin ise türetilmiş sınıflar içerisinde tanımlanması (metot gövdesinin yazılması) gerekmektedir.

Abstract sınıflarda metot bildirimi yapabilmek için erişim belirtecinden sonra “abstract” anahtar sözcüğünü yazmamız gerekmektedir.

public class TuretilmisSinif : TemelSinif
{
    public override void Metot_2()
    {
        Console.WriteLine("Türetilmiş sınıf içerisinde tanımlanmış metot.");
    }
}

TuretilmisSınıf, TemelSinif‘dan türetildiği için TuretilmisSınıf içerisinde Metot_2()‘nin tanımlamasını yapmamız gerekmektedir. Bu örnekten de anlaşılacağı üzere abstract sınıfları arayüzler (interface) gibi kullanabiliriz.

Abstract sınıf içerisinde bildirimi yapılmış olan metotları (Bkz: Metot_2()) türetilmiş sınıflar içerisinde tanımlayabilmemiz için override anahtar sözcüğünü kullanmamız gerekmektedir.

class Program
{
    private static void Main(string[] args)
    {
        TuretilmisSinif turetilmisSinif = new TuretilmisSinif();
        turetilmisSinif.Metot_1();
        turetilmisSinif.Metot_2();
    }
}

Ekran çıktısı:
Abstract sınıf içerisinde tanımlanmış metot.
Türetilmiş sınıf içerisinde tanımlanmış metot.
Press any key to continue . . .

Abstract Sınıfların Normal Sınıflardan Farkı Nedir?

Fark 1: Normal sınıflar içerisinde metot bildirimi yapılamazken, Abstract sınıflar içerisinde tıpkı arayüzler de olduğu gibi metot bildirimi yapılabilir. Bildirimi yapılan metotlar, Abstract sınıftan türeyen sınıflar içerisinde tanımlanmak zorundadır.

Fark 2: Normal sınıflardan “new()” anahtar sözcüğü ile nesneler oluşturulabilir ancak Abstract sınıflar tamamen kalıtım amaçlı geliştirildiğinden Abstract sınıflardan nesne oluşturulamaz.

TuretilmisSinif turetilmisSinif = new TuretilmisSinif(); (Doğru)
TemelSinif temelSinif = new TemelSinif();                (Yanlış)

Abstract Sınıfların Arayüzlerden Farkı Nedir?

Fark 1: Arayüzlerde sadece metot bildirimi yapılabilirken Abstract sınıflarda hem metot bildirimi yapılabilir hem de metot tanımlanabilir.

Fark 2: Bir sınıfa sadece bir tane Abstract sınıf inherit edilebilir ancak aynı sınıfa birden fazla arayüz implement edilebilir.

public abstract class AbstractSinif_1
{
    public abstract void Metot_1();
}

public abstract class AbstractSinif_2
{
    public abstract void Metot_2();
}

public class TuretilmisSinif : AbstractSinif_1
{
    public override void Metot_1()
    {
        Console.WriteLine("Metot_1() tanımlandı.");
    }
}

TuretilmisSinif‘a Abstract sınıflardan sadece bir tanesini inherit edebiliriz. Yani TuretilmisSinif‘a hem AbstractSinif_1‘i hem de AbstractSinif_2‘yi inherit edemeyiz.
public class TuretilmisSinif : AbstractSinif_1 , AbstractSinif_2 {} // YANLIŞ!

Ancak bir Abstract sınıfa, başka bir Abstract sınıfı inherit ederek bu kısıtı kaldırmak mümkün.
public abstract class AbstractSinif_1 : AbstractSinif_2
{
    public abstract void Metot_1();
}

public abstract class AbstractSinif_2
{
    public abstract void Metot_2();
}

public class TuretilmisSinif : AbstractSinif_1 
{
    public override void Metot_1()
    {
        Console.WriteLine("Metot_1() tanımlandı.");
    }

    public override void Metot_2()
    {
        Console.WriteLine("Metot_2() tanımlandı.");
    }
}

AbstractSinif_1‘e AbstractSinif_2 inherit edildiği için, AbstractSinif_1‘den türetilen bir sınıf hem AbstractSinif_1 içerisinde bildirimi yapılmış olan metodu (Metot_1()) hem de AbstractSinif_2 içerisinde bildirimi yapılmış olan metodu (Metot_2()) içermek zorundadır.

Abstract Sınıflara Hangi Durumlar da İhtiyaç Duyarız

Yılın her bir ayı için bir sınıf oluşturmamız gerektiğini düşünelim ve bu sınıflar içerisinde ilgili ayın kaç günden oluştuğu, yılın kaçıncı ayı olduğu ve her bir gününün kaç saatten oluştuğu bilgilerini döndürecek metotların olmasını istediğimizi varsayalım.

public interface Ay
{
    int GunlerKacSaattir();
    int KacGundenOlusur();
    int YilinKacinciAyidir();
}

İlk başta aklımıza bir arayüz kullanmak gelebilir. Ay isminde bir arayüz tanımlayıp, içerisinde ihtiyacımız olan metotların bildirimini yaptıktan sonra bu arayüzü aylarımızı temsil eden sınıflara implement ederek bir çözüm geliştirebiliriz.
public class Ocak : Ay
{
    public int GunlerKacSaattir()
    {
        return 24;
    }
   
    public int KacGundenOlusur()
    {
        return 31;
    }

    public int YilinKacinciAyidir()
    {
        return 1;
    }
}

public class Subat : Ay
{
    public int GunlerKacSaattir()
    {
        return 24;
    }

    public int KacGundenOlusur()
    {
        return 28;
    }

    public int YilinKacinciAyidir()
    {
        return 2;
    }
}

public class Aralik : Ay
{
    public int GunlerKacSaattir()
    {
        return 24;
    }

    public int KacGundenOlusur()
    {
        return 31;
    }

    public int YilinKacinciAyidir()
    {
        return 12;
    }
}

Ancak ayları temsil eden sınıfları dikkatlice inceleyecek olursak, GunlerKacSaattir() metodunun hepsinde aynı sonucu döndürecek şekilde tanımlandığını görebiliriz. Her bir ay için aynı metodu tekrar tekrar tanımlayıp kod tekrarı yapmak yerine, bir defaya mahsus tanımlamak çok daha verimli olacaktır. Arayüzler içerisinde metot tanımı yapılamayacağından, bu örnekte arayüz yerine abstract sınıf kullanacağız.
public abstract class Ay
{
    public int GunlerKacSaattir()
    {
        return 24;
    }

    public virtual int KacGundenOlusur()
    {
        return 31;
    }

    public abstract int YilinKacinciAyidir();
}

İçerisinde hem metot tanımı hem de metot bildirimi yapabileceğimiz bir yapıya ihtiyacımız olduğundan Ay isminden bir abstract sınıf oluşturduk. GunlerKacSaattir() metodu tüm aylar için aynı sonucu döndüreceğinden, bu metodu direkt abstract sınıf içerisinde tanımladık. Böylece 12 ay için aynı metodu 12 defa yazmak yerine, bir defa yazmış olduk.

Eğer bir metot tüm türetilmiş sınıflarda aynı şekilde tanımlanıyorsa o metodu abstract sınıf içerisinde standart bir metot tanımlar gibi tanımlarız.

KacGundenOlusur() metodunu abstract sınıf içerisinde tanımlamak yerine sadece bildirimini yapıp, Ayları temsil eden sınıflarımız içerinde tanımlayabilirdik. Ancak bu metot Ocak, Mart, Mayıs, Temmuz, Ağustos, Ekim ve Aralık ayları için aynı sonucu (31) döndürmesi gerektiğinden, en azından bu aylar için tekrardan KacGundenOlusur() metodunun tanımını yapmamak adına varsayılan olarak geriye 31 döndüren ancak istenilen sınıf içerisinde override edilerek yeniden tanımlanabilecek şekilde kullanılabilmesi için virtual olarak tanımladık (Bkz: C# – Virtual Metotlar).

Eğer bir metot bir çok türetilmiş sınıfta aynı şekilde tanımlanıyor iken sadece bir kaçında değişik şekilde tanımlanıyorsa o metot abstract sınıf içerisinde virtual olarak tanımlanır.

YilinKacinciAyidir() metodu her bir ay için farklı bir değer döndüreceğinden, bu metodun sadece bildirimi yapıyoruz.

Eğer bir metot tüm türetilmiş sınıflarda farklı şekilde tanımlanıyorsa o metodun abstract sınıf içerisinde sadece bildirimi yapılır.

public class Ocak : Ay
{            
    public override int YilinKacinciAyidir()
    {
        return 1;
    }
}

public class Subat : Ay
{
    public override int KacGundenOlusur()
    {
        return 28;
    }

    public override int YilinKacinciAyidir()
    {
        return 2;
    }
}

public class Aralik : Ay
{
    public override int YilinKacinciAyidir()
    {
        return 12;
    }
}

Serdar YILMAZ

C# – Virtual Metotlar

.netKalıtım yolu ile sınıfların birbirinden türetilebileceğini ve bir sınıfın diğer bir sınıftan türediği zaman, türediği sınıfın bütün özelliklerini içereceğini “C# – Kalıtım” başlıklı içeriğimizde açıklamıştık.

Temel sınıftan türetilmiş sınıflara aktarılan metotları her zaman olduğu gibi kullanmak istemeyebiliriz. Bu metotları türetilmiş sınıf içerisinde yeniden tanımlayabilmek için virtual ve override anahtar sözcüklerini kullanırız.

Virtual metotlar kalıtım yolu ile aktarıldıkları sınıfların içerisinde override edilerek değiştirilebilirler. Eğer override edilmezlerse temel sınıf içerisinde tanımlandıkları şekilde çalışırlar.

Kod kalabalığını arttırmamak ve konuyu daha anlaşılır bir şekilde anlatmak için; sınıfları karışıklığa yer vermeyecek şekilde isimlendirip, konumuzun dışında olan kod satırlarına/bloklarına yer vermemeye çalışacağım.

Örnek bir senaryo üzerinden gidecek olursak; User (kullanıcı), Category (kategori) ve Article (Makale) isimli sınıflarımızın olduğunu ve bu sınıfların Database sınıfından türetildiğini düşünelim.

public class Database
{
    public void Insert(string data)
    {
        Console.WriteLine("{0} Kaydedildi.", data);
    }
}

public class User : Database
{

}

public class Category : Database
{

}

public class Article : Database
{

}

Database sınıfı içerisindeki Insert() metodu kalıtım yolu ile User, Category ve Article sınıflarına aktarılacaktır.

class Program
{
    static void Main(string[] args)
    {
        User user = new User();
        user.Insert("Serdar_Yilmaz");

        Category category = new Category();
        category.Insert("Programlama");

        Article article = new Article();
        article.Insert("C# - Virtual Metotlar");
    }
}

Yukarıdaki konsol uygulamasının ekran çıktısı;

Serdar_Yilmaz Kaydedildi.
Programlama Kaydedildi.
C# - Virtual Metotlar Kaydedildi.
Press any key to continue . . .

Ancak biz User sınıfında Insert() metodunun kullanıcı adı kontrolü yaptıktan sonra veriyi kaydetmesini istiyor olabiliriz. Temel sınıf içerisindeki bir metodun gövdesini türetilmiş sınıflar içerisinde değiştirebilmek için Temel sınıf içerisindeki metodu virtual olarak tanımlamamız gerekir.

public class Database
{
    public virtual void Insert(string data)
    {
        Console.WriteLine("{0} Kaydedildi.", data);
    }
}

public class User : Database
{
    public override void Insert(string data)
    {
        if (data=="Serdar_Yilmaz")
        {
            Console.WriteLine("{0} Zaten Kayıtlı.", data);
        }
        else
        {
            base.Insert(data);
        }
    }
}

public class Category : Database
{

}

public class Article : Database
{

}

Database sınıfı içerisindeki Insert() metodu virtual olarak belirtildiği için User sınıfı içerisinde override edilerek gövdesi değiştirildi. override edilmiş bir metot içerisinden metodun orijinalini (temel sınıf içerisindeki halini) çağırabilmek için base anahtar sözcüğü kullanılır.

class Program
{
    static void Main(string[] args)
    {
        User user = new User();
        user.Insert("Serdar_Yilmaz");

        Category category = new Category();
        category.Insert("Programlama");

        Article article = new Article();
        article.Insert("C# - Virtual Metotlar");
    }
}

Konsol uygulamamızı tekrar çalıştıracak olursak ekran çıktısı aşağıdaki gibi olacaktır.

Serdar_Yilmaz Zaten Kayıtlı.
Programlama Kaydedildi.
C# - Virtual Metotlar Kaydedildi.
Press any key to continue . . .

Serdar YILMAZ

C# – Arayüzler

.netArayüzler, sınıflara rehberlik etmek üzere oluşturulan nesneye dayalı programlamanın en önemli özelliklerinden biridir. Sınıfların hangi metotları ve özellikleri içermesi gerektiğini arayüzler içerisinde bildiriyoruz.

Arayüz Oluşturma

interface IKisi
{
    string adSoyad { get; set; }
    string adres { get; set; }
    string departman { get; set; }
    void bilgi();
}

Arayüzler “interface” anahtar sözcüğü ile oluşturulur. Zorunlu olmamakla birlikte arayüz isimleri genellikle “I” harfiyle başlatılır. Böylece “I” ön ekini gören bir programcı onun bir arayüz olduğunu anlar. Arayüz içerisinde özelliklerin ve metotların sadece bildirimi yapılır. Yani herhangi bir şekilde özelliklere bir değer atanmaz sadece türleri ve isimleri yazılır, aynı şekilde metotların içerisine kodlar yazılmaz sadece geri dönüş türleri ve isimleri yazılır. 

Arayüzlerin Sınıflara İmplement Edilmesi

class Yonetici : IKisi
{
    public string adSoyad { get; set; }
    public string adres { get; set; }
    public string departman { get; set; }

    public void bilgi()
    {
        Console.WriteLine(" {0} isimli çalışan {1} departmanında yöneticidir.",adSoyad,departman);
    }
}

IKisi arayüzü, Yonetici sınıfına implement edildiği için (1.Satır) Yonetici sınıfı, IKisi arayüzünde bildirimi yapılmış olan özellikleri ve metotları içermek zorundadır. Yonetici sınıfı içerisine, IKisi arayüzünde bildirimi yapılmış metotlar ve özellikler haricinde Yonetici sınıfına has metotlar ve özellikler de tanımlanabilir. 

Arayüzlere Neden İhtiyaç Duyarız?

Arayüzlerin geniş bir kullanım alanı bulunmaktadır. Basit bir örnek üzerinden anlatacak olursak; 

interface IKisi
{
    string adSoyad { get; set; }
    string adres { get; set; }
    string departman { get; set; }
    void bilgi();
}

class Yonetici : IKisi
{
    public string adSoyad { get; set; }
    public string adres { get; set; }
    public string departman { get; set; }

    public void bilgi()
    {
        Console.WriteLine(" {0} isimli çalışan {1} departmanında yöneticidir.",adSoyad,departman);
    }
}

class Isci : IKisi
{
    public string adSoyad { get; set; }
    public string adres { get; set; }
    public string departman { get; set; }

    public void bilgi()
    {
        Console.WriteLine(" {0} isimli çalışan {1} departmanında işçidir.", adSoyad, departman);
    }
}

Arayüz kullanımı sınıflarımızı bir standart çerçevesinde yapılandırmamızı sağlamaktadır. IKisi arayüzünün Isci ve Yonetici sınıflarına implement edildiğini gören bir programcı, bu sınıfların içerisinde adSoyad, adres, departman gibi özelliklerin olduğunu ve bu özelliklere erişilerek gerekli bilgilerin alınabileceğini bilecektir.

Örneğin, Yonetici ve Isci sınıflarından oluşturulan nesnelerin içerisindeki adSoyad özelliğini ekrana yazdıran bir metot yazalım. Arayüzlerin sağlamış olduğu kolaylıktan faydalanmıyor olsaydık bu işlemi aşağıdaki gibi yapıyor olurduk;

class Program
{
    public void adSoyadBilgisi(Yonetici yonetici)
    {
        Console.WriteLine(yonetici.adSoyad);
    }

    public void adSoyadBilgisi(Isci isci)
    {
        Console.WriteLine(isci.adSoyad);
    }
}

İki farklı metot tanımlamamız gerekirdi. Bu metotlardan biri Yonetici sınıfı türünden parametre alırken, diğeri Isci sınıfı türünden parametre alırdı. Ancak arayüzler sayesinde bu işlemi tek bir metot yazarak yapabiliriz.

class Program
{
    public void adSoyadBilgisi(IKisi kisi)
    {
        Console.WriteLine(kisi.adSoyad);
    }
}

IKisi arayüzü, Yonetici ve Isci sınıflarına implement edildiği için bu sınıflardan oluşturulan nesneler, IKisi arayüzü türündeki bir parametrede tutulabilir. Bu yüzden adSoyadBilgisi() metoduna parametre olarak Yonetici veya Isci sınıfından oluşturulan nesneleri gönderebiliriz. Bu parametre aracılığı ile ilgili nesnelerin sadece IKisi arayüzünden gelen özellik ve metotlarına erişebiliriz.

Çoklu İmplementasyon 

Bir sınıfa birden fazla arayüz implement edilebilir. Örnek bir senaryo üzerinden konuyu anlatacak olursak; Bir fabrika için otomasyon programı yazdığımızı düşünelim. Fabrikada 3 farklı çalışan türü olsun, bunlar yönetici, işçi ve robot. Her bir çalışanın ID, ad-soyad, adres, maaş, departman ve toplam çalışma saati bilgisi olsun. 

interface ICalisan
{
    int id { get; set; }
    string adSoyad { get; set; }
    string adres { get; set; }
    double maas { get; set; }
    string departman { get; set; }
    ulong toplamCalismaSaati { get; set; }
}

Eğer arayüz tasarımını yukarıdaki gibi yaparsak ICalisan arayüzünü, oluşturacağımız Yonetici ve Isci sınıflarına implement edebiliriz ancak Robot sınıfına implement edemeyiz. Çünkü robotların adlarının ve adreslerinin olmayacağını ve maaş almayacaklarını biliyoruz. Bu yüzden ICalisan arayüzünü uygun bir şekilde parçalamamız gerekiyor. 

interface ICalisan
{
    int id { get; set; }
    string departman { get; set; }
    ulong toplamCalismaSaati { get; set; }
}

interface IKisi
{
    string adSoyad { get; set; }      
    string adres { get; set; }
    double maas { get; set; }
}

ICalisan ve IKisi şeklinde iki arayüz oluşturduk. Yöneticiler ve işçiler hem çalışan hemde birer kişi olduğundan, bu sınıflara hem ICalisan arayüzünü hem de IKisi arayüzünü imlement edeceğiz. Robot sınıfına ise sadece ICalisan arayüzünü implement edeceğiz. 

class Yonetici : ICalisan, IKisi
{
    public int id { get; set; }
    public string adSoyad { get; set; }
    public string adres { get; set; }
    public double maas { get; set; }
    public string departman { get; set; }
    public ulong toplamCalismaSaati { get; set; }  
}

class Isci : ICalisan, IKisi
{
    public int id { get; set; }
    public string adSoyad { get; set; }
    public string adres { get; set; }
    public double maas { get; set; }
    public string departman { get; set; }
    public ulong toplamCalismaSaati { get; set; }
}

class Robot : ICalisan
{
    public int id { get; set; }
    public string departman { get; set; }
    public ulong toplamCalismaSaati { get; set; }
}

Kurumsal Mimarilerde Arayüz Kullanımı

Daha önce kurumsal mimaride bir uygulama geliştirmediyseniz bu bölümü şimdilik atlayabilirsiniz. 

Bu başlıkta olabildiğince yalın bir şekilde arayüzlerin kurumsal mimarilerde ki kullanımına bir örnek vermeye çalışacağım. Kod kalabalığını arttırmamak ve konuyu daha anlaşılır bir şekilde anlatmak için sınıfları karışıklığa yer vermeyecek şekilde isimlendirip, konumuzun dışında olan kod satırlarına/bloklarına (veritabanı sorguları) yer vermemeye çalışacağım.

Bir projede farklı veritabanı yönetim sistemleri kullanılabilir. Projelerimizi bizden fazla veritabanı yönetim sistemine destek verecek şekilde geliştirebilmek için arayüzlerden faydalanabiliriz. 

public interface IRepository
{
    void insert();
    void update();
    void delete();
    void save();
}

Öncelikle IRepository adını verdiğimiz arayüzün içerisinde, veritabanı üzerinde ekleme, silme, güncelleme, kaydetme gibi temel işlemleri yapacak metotların bildirimini yapıyoruz. Sonra bu arayüzü, farklı veritabanı yönetim sistemleri üzerinde işlem yapacak olan sınıflara implement ediyoruz. 

public class MsSQLDB : IRepository
{
    public void delete()
    {
        // MsSQL veritabanında silme işlemini gerçekleştirecek kodlar.
        Console.WriteLine("MsSQL -> delete() metodu çalıştı.");         
    }

    public void insert()
    {
        // MsSQL veritabanına ekleme işlemini gerçekleştirecek kodlar.
        Console.WriteLine("MsSQL -> insert() metodu çalıştı.");
    }

    public void save()
    {
        // MsSQL veritabanına kaydetme işlemini gerçekleştirecek kodlar.
        Console.WriteLine("MsSQL -> save() metodu çalıştı.");
    }

    public void update()
    {
        // MsSQL veritabanında güncelleme işlemini gerçekleştirecek kodlar.
        Console.WriteLine("MsSQL -> update() metodu çalıştı.");
    }
}

public class OracleDB : IRepository
{
    public void delete()
    {
        // Oracle veritabanında silme işlemini gerçekleştirecek kodlar.
        Console.WriteLine("Oracle -> delete() metodu çalıştı.");
    }

    public void insert()
    {
        // Oracle veritabanına ekleme işlemini gerçekleştirecek kodlar.
        Console.WriteLine("Oracle -> insert() metodu çalıştı.");
    }

    public void save()
    {
        // Oracle veritabanına kaydetme işlemini gerçekleştirecek kodlar.
        Console.WriteLine("Oracle -> save() metodu çalıştı.");
    }

    public void update()
    {
        // Oracle veritabanında güncelleme işlemini gerçekleştirecek kodlar.
        Console.WriteLine("Oracle -> update() metodu çalıştı.");
    }
}

Projemizin hem Oracle veritabanına hemde MsSQL veritabanına destek vermesini istiyorsak; IRepository arayüzü içerisinde bildirimi yapılan metotları, OracleDB sınıfında Oracle vertabanında işlem yapacak şekilde, MsSQLDB sınıfında MsSQL veritabanında işlem yapacak şekilde yazmamız gerekiyor.

MsSQLDB ve OracleDB sınıfları ile artık hem MsSQL veritabanında hem de Oracle veritabanında işlem yapabiliyoruz.

public class ExampleManager : IRepository
{
    private IRepository database;
    public ExampleManager(IRepository db)
    {
        database = db;
    }

    public void delete()
    {
        database.delete();
    }

    public void insert()
    {
        database.insert();
    }

    public void save()
    {
        database.save();
    }

    public void update()
    {
        database.update();
    }
}

Son olarak ExampleManager isimli bir sınıf oluşturuyoruz, bu sınıftan nesne oluştururken yapıcı metoduna parametre olarak MsSQLDB sınıfından bir nesne gönderirsek ekleme, silme, güncelleme, kaydetme işlemini MsSQL veritabanı üzerinde yapacaktır, OracleDB sınıfından bir nesne gönderirsek de Oracle veritabanı üzerinde yapacaktır. 

class Program
{      
    static void Main(string[] args)
    {
        ExampleManager example_1 = new ExampleManager(new MsSQLDB());
        example_1.insert();
        example_1.delete();

        Console.WriteLine(new string('-', 40));

        ExampleManager example_2 = new ExampleManager(new OracleDB());
        example_2.insert();
        example_2.delete();
    }
}

Ekran Çıktısı:

MsSQL -> insert() metodu çalıştı.
MsSQL -> delete() metodu çalıştı.
----------------------------------------
Oracle -> insert() metodu çalıştı.
Oracle -> delete() metodu çalıştı.
Press any key to continue . . .

Serdar YILMAZ

jQuery ve Yazım Kuralları

jquery logojQuery, 2006 yılında John Resig tarafından geliştirilmiş açık kaynak kodlu bir JavaScript kütüphanesidir. Web geliştiricilerinin sıkça başvurduğu JavaScript komutlarını ve fonksiyonlarını hazır bir şekilde sunmaktadır.

jQuery Kütüphanesinin Sayfaya Eklenmesi

jQuery kütüphanesindeki komutları ve fonksiyonları kullanabilmek için kütüphaneyi sayfaya eklememiz gerekmektedir. Kütüphaneyi sayfaya eklemek için konumunu <head> </head> etiketi arasında bildiriyoruz.

<!DOCTYPE html>
<html>
<head>
	<title>jQuery</title>
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
	<h1>jQuery Dersleri - Srdrylmz.com</h1>
</body>
</html>

Yukarıdaki örnekte jQuery kütüphanesini Google’un CDN sunucularından çekerek sayfamıza ekledik. Dilersek jQuery kütüphanesini “jquery.com” adresinden bilgisayarımıza indirip, dosya konumunu “src” özelliğinde belirterek de kullanabiliriz.

<script src="script/jquery.min.js"></script>

jQuery Yazım Kuralları

JavaScript dilinin bütün yazım kuralları jQuery kütüphanesi içinde geçerlidir.

jqueryEk olarak; yukarıdaki örnekte de görüldüğü gibi jQuery kodlarımız temel olarak “$” işareti, seçici, kullanılacak metod ve metoda gönderilecek parametre bilgisinden oluşmaktadır. Parantezler içerisindeki tırnaklar tek(‘) veya çift(“) tırnak olabilir. Temiz bir kodlama için kod satırları “;” ile bitirilmelidir. Ayrıca aşağıdaki örnekte olduğu gibi jQuery’de metotlar art arda yazılabilir.

$('div').html('<b>Serdar YILMAZ</b>').height(30).width(100);

jQuery kodlarının, DOM yüklendikten sonra çalışması için kodlarımızı aşağıdaki kod blogunun arasına yazmaktayız. Böylece olası bir çok hatayı önlemiş olacağız.
$(document).ready(function(){
    // jQuery Kodları
});

Fonksiyonların Yazım Şekli ve Kullanımı

Aşağıdaki örnekte 3 farklı şekilde fonksiyon tanımlandık.

<!DOCTYPE html>
<html>
<head>
	<title>jQuery</title>
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

	<script>
		$(document).ready(function(){
			// Yazım Şekli 1
			function topla(sayi1,sayi2){				
				alert(sayi1+sayi2);
			};	

			// Yazım Şekli 2
			$.carp = function(sayi1,sayi2){
				alert(sayi1*sayi2);
			};
			
			// Yazım Şekli 3
			$.islem = {
				cikar: function(sayi1,sayi2){
					alert(sayi1-sayi2);
				},
					
				karesi: function(sayi1){
					alert(sayi1*sayi1);
				}
			};
		});		
	</script>
</head>
<body>
	<!-- HTML SAYFAMIZIN GÖVDESİ-->
</body>
</html>

Tanımlamış olduğumuz fonksiyonların çağrımı aşağıdaki gibidir.
topla(4,5);
$.carp(3,8);
$.islem.cikar(9,5);
$.islem.karesi(8);

Serdar YILMAZ