C# etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
C# etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

C# Anonymous Type Kullanımı

Bir sınıf tanımladığımızda bu sınıfı programımızın bir çok yerinden çağırılmasını bekleriz. Peki sadece belli bir scope içerisinde sadece 1 kere kullanacağımız bir model gerekiyorsa ne yaparız? Sadece 1 kere kullanacak olmamıza rağmen yeni sınıf oluşturmak ne kadar mantıklı? Tabi ki mantıklı değil bu tip durumlarda Anonymous Type kullanabiliriz.

Anonymous Type tanımı yapılırken var keyword'ü kullanılır. Çünkü bildiğiniz gibi var ile bir değişken tanımlandığında tipi runtime'da compiler tarafından belirlenir. Anonymous Type olarak tanıladığımız tiplerin belirli bir tipi olmadığı için var ile kullanılır.

    class Program
    {
        static void Main(string[] args)
        {
            BuildAnonymousType("Safak", "Unel", 26);
            Console.ReadLine();
        }

        static void BuildAnonymousType(string name, string surname, int age)
        {
            // Build anon type using incoming args.
            // Anonymous Type tanimi yapiliyor
            // new'den sonra isim verilmeden property'ler tanimlaniyor
            var person = new { Name = name, Surname = surname, Age = age };

            // Note you can now use this type to get the property data!
            Console.WriteLine("Fullname: {0} Age: {1}\n",
            person.Name + " " + person.Surname, person.Age);

            // Anon types have custom implementations of each virtual
            // method of System.Object. For example:
            Console.WriteLine("ToString() == {0}", person.ToString());
        }
    }

Yukarıda gördüğünüz gibi Person sınıfı oluşturmak yerine Anonymous Type ile person nesnesi yarattık, ardından yarattığımız Anonymous Type nesnesinin property'lerine metod içerisinde ulaşabildik.

Çıktı:




Anonymous Type tanımlanırken dikkat edilmesi gereken bir konu, Anonymous Type propertyleri default construtor ile tanımlanır ve readonly'dirler sonradan değiştirilemezler.
 
            person.Name = "Ahmet";

Yarattığımız person nesnesinin Name alanını sonradan yukarıdaki gibi Ahmet'e çevirmek istersek readonly olduğu için aşağıdaki hatayı verecektir.
Property or indexer 'AnonymousType#1.Name' cannot be assigned to -- it is read only
Peki bu yarattığımız Anonymous Type nesnesini compiler nası yoruluyor hangi tipe çeviriyor? Bunu görmek için şu kod parçasını yazıp çalıştıralım:

 
        static void ReflectOverAnonymousType(object obj)
        {
            Console.WriteLine("obj is an instance of: {0}", obj.GetType().Name);
            Console.WriteLine("Base class of {0} is {1}",
              obj.GetType().Name,
              obj.GetType().BaseType);
            Console.WriteLine("obj.ToString() == {0}", obj.ToString());
            Console.WriteLine("obj.GetHashCode() == {0}", obj.GetHashCode());
            Console.WriteLine();
        }

Çıktı:



Gördüğünüz gibi compiler arka tarafta System.Object sınıfından türeyen <>f__AnonymousType0 isminde bir sınıf yaratmış. Programımızı ildasm ile açarsanız bu sınıfın sealed bir sınıf olduğunu görürsünüz. Ayrıca belirttiğimiz tüm Property'lerin bu sınıf içerisinde readonly olarak yaratılmış olduğunu göreceksiniz. Bu yüzden construtor dışında bu property'leri değiştiremiyoruz.

<>f__AnonymousType0 siz kendi programınızda baktığınızda farklı olabilir. Çünkü compiler çalışma zamanında oluşturduğu bu sınıfa unique bir isim vermektedir.

Son olarak aşağıdaki gibi 2 Anonymous Type tanımladığımızı düşünelim:

            var p1 = new { Name = "Safak", Surname = "Unel", Age = 26 };
            var p2 = new { Name = "Ahmet", Surname = "Mehmet", Age = 22 };

Compiler bu iki nesne için oluşturduğu tek <>f__AnonymousType0 sınıfını kullanacaktır. Her nesne için ayrı sınıf oluşturmayacaktır. Property'leri aynı olan Anonymous Type nesneleri için tek bir sealed sınıf oluşturulup kullanılmaktadır.

Örnek Proje:
AnonymousTypeExample.rar

C# Implicit, Explicit Conversions Kullanımı

Programlarınızı yazarken bir çok kez bir tipten diğer tipe çevirim yapmışsınızdır. Bu çevrimlere örnek verirsek long tipinden int tipine, object tipinden string tipine gibi. Bu çevrimler sırasında daha geniş olan örneğin long'tan int'e çevrim yaparken data kaybı yaşanabilir. Ama int'i long'a çevririsek bu data kaybı yaşanmaz.



Data kaybı yaşanmayan çevrim Implicit, data kaybı yaşanabilecek çevrim ise Explicit olarak adlandırabiliriz. Implicit Conversation aritmetik operatörler ile yapılır bir cast operatörü içermez. Explicit Conversation ise cast operatörü ile yapılan coversion işlemidir.

            int a = 123;
            long b = a; // Implicit conversion from int to long.
            int c = (int)b; // Explicit conversion from long to int.

Aynı zamanda custom sınıflarımız içerisinde Base, Derived ilişkisi içerisinde bu çevrimler yapılabilir.
    class Base { }
    class Derived : Base { }
    class Program
    {
        static void Main(string[] args)
        {
            // Implicit: derived to base.
            Base myBaseType;
            myBaseType = new Derived();
            // Must explicitly cast to store base reference
            // in derived type.
            Derived myDerivedType = (Derived)myBaseType;
        }
    }

Peki custom sınıflarımız arasında Base, Derived ilişkisi olmadığı halde iki farklı sınıf önerğini birbirlerine çevirmek istesek ne yapacağız? Örnek biraz saçma olsada öğrenmek için veriyorum. Dikdörtgen ve Cember sınıflarımız olsun. Dikdörtgen'i cembere çevirmek istiyoruz.

    public struct Square
    {
        public int Length { get; set; }
        public Square(int l)
            : this()
        {
            Length = l;
        }
        public void Draw()
        {
            for (int i = 0; i < Length; i++)
            {
                for (int j = 0; j < Length; j++)
                {
                    Console.Write("*");
                }
                Console.WriteLine();
            }
        }
        public override string ToString()
        { return string.Format("[Length = {0}]", Length); }
    }
    public struct Rectangle
    {
        public int Width { get; set; }
        public int Height { get; set; }
        public Rectangle(int w, int h)
            : this()
        {
            Width = w; Height = h;
        }
        public void Draw()
        {
            for (int i = 0; i < Height; i++)
            {
                for (int j = 0; j < Width; j++)
                {
                    Console.Write("*");
                }
                Console.WriteLine();
            }
        }
        public override string ToString()
        {
            return string.Format("[Width = {0}; Height = {1}]",
            Width, Height);
        }
    }

Rectangle ve Square adında 2 struct yapısı tanımladık. Şimdi bu 2 yapı arasında şöyle bir çevrim yapmak istesek:

            Rectangle r = new Rectangle(15, 4);
            Square s = (Square)r;

Rectangle nesnesini Square nesnesine çevirmeye çalıştık. Fakat bu 2 nesne arasında Base, Derived ilişkisi oladığı için aşağıdaki hatayı alacağız:

Cannot convert type 'CustomTypeConversions.Rectangle' to 'CustomTypeConversions.Square'
Compiler bize Rectangle'ı Square'e çeviremediğini belirten bir hata mesajı verdi. Peki bunu custom bir şekilde nasıl yapabiliriz şimdi buna bakalım. C#'ta custom çevrim işlemleri için kullanılan explicitimplicit keywordleri vardır.

Yukarıdaki çevrim explicit bir çevrim olduğu için explicit keywordunu şu şekilde kullanarak hata almadan çevrim işlemini gerçekleştirebiliriz. Aşağıdaki kodu Square sınıfımıza eklersek:

        // Explicit: Rectangle'in Height propertysini kullanarak
        // Square olusturuyor
        public static explicit operator Square(Rectangle r)
        {
            Square s = new Square();
            s.Length = r.Height;
            return s;
        }

Artık
            Rectangle r = new Rectangle(15, 4);
            Square s = (Square)r;

Bu kod hatasız bir şekilde çalışacak ve r Rectangle nesnesinin Height property değeri olan 4'u kullanarak Length'i 4 olan bir squere nesnesi çevrimi yapılacaktır. Gördüğünüz gibi explicit olarak Rectangle nesnesinden Square nesnesine çevrimi gerçekleştirdik.

Aşağıdaki gibi istediğiniz gibi farklı çevrimlerde yazabilirsiniz:

        // int tipini Square nesnesine çevirir
        // Example: Square s = (Square)90;
        public static explicit operator Square( int sideLength )
        {
            Square newSq = new Square();
            newSq.Length = sideLength;
            return newSq;
        }

        // Square nesnesini int tipini çevirir
        // Example: int a = (Square)s;
        public static explicit operator int( Square s )
        { return s.Length; }

Peki şöyle bir çevrim yapmak istesek ne olacak?

            Square s3 = new Square();
            s3.Length = 83;

            // implicit cevrim
            Rectangle rect2 = s3;

Bu durumda compiler yine hata verecektir. Çünkü yapmaya çalıştığımız çevrim implicit fakat biz Rectangle sınıfımıza implicit çevrimi nasıl yapacağını belirten bir şey yazmadık.

Cannot implicitly convert type 'CustomTypeConversions.Square' to 'CustomTypeConversions.Rectangle'
implicit işlemini yapmak için implicit keyword'ünü kullanarak sınıfımıza bu çevrimi nasıl yapacağını söylememiz gerekiyor. Aşağıdaki kodu Recangle sınıfımıza eklersek:

        // implicit cevrim Square to Rectangle
        public static implicit operator Rectangle(Square s)
        {
            return new Rectangle(s.Length + 10, s.Length + 5);
        }
Artık

            Square s3 = new Square();
            s3.Length = 83;

            // implicit cevrim
            Rectangle rect2 = s3;

Bu kod hata almadan çalışacaktır. Bu şekilde implicit şekilde Squere nesnemizi Rectangle nesnemize çevirmeyi başardık.

Örnek Proje:
CustomTypeConversions.rar

C# Extension Methods Kullanımı

Extension metodlar.NET 3.5 ile hayatımıza girmiş bir kavramdır. Kısaca bir sınıf veya struct yapısını modifiye etmeden yeni metodlar veya property'ler eklememize olanak sağlar.

Örneğin production'da çalışan ve bir çok farklı uygulamanın kullandığı bir sınıfa yeni bir metod eklenmeniz istendi. Ama bu metod sadece sizin üzerinizde çalıştığınız uygulama için gerekli ama diğer uygulamalar için gereksiz. Bu durumda ne yapabilirsiniz?
  • Production'da çalışan sınıfa istenilen metodu eklemek

Aklımıza ilk gelen herhalde bu sınıfa istenilen metodu eklemek olacaktır. Fakat bu sınıf bir çok uygulama tarafından kullanıldığı için sizin üzerinde yapacağınız değişiklikler belkide diğer uygulamaların patlamasına neden olacaktır.

  • Yeni bir sınıf oluşturup bu sınıfı production'da çalışan sınıftan türetmek
Bu yöntem ile productionda çalışan sınıfı değiştirmemiş olacağız aslında mantıklı bir yaklaşım oldu fakat bu sefer elimizde 2 sınıf oldu. Bunun kontrolü, bakımı problem olacaktır. Ayrıca sınıfımız sealed sınıfta olabilir biliyorsunuz ki sealed sınıflardan inherit yapılamaz.

Bu iki yöntemde işimize yaramadı. Bu noktada extension metodlar yarımımıza koşuyor.

Extension metodlarla ilgili olarak;
  • Extension metodlar static tanımlanmalıdır. 
  • Extension metodların ilk parametresinin önünde this olmalıdır ve extend edilecek sınıf türü buraya yazılmalıdır. Bu extension metodun hangi sınıfı extend ettiğini belirtir.
Şimdi object ve int sınıfları için 2 extension metod tanımı yapalım:

    public static class MyExtensions
    {
        public static void DisplayObjectAssemblyInfo(this object obj)
        {
            Console.WriteLine("Obje Type: {0}, Assembly: \n"
                , obj.GetType().Name, 
                Assembly.GetAssembly(obj.GetType()).GetName().Name);
        }

        public static int Square(this int i)
        {
            return i * i;
        }
    }

MyExtensions sınıfı içerisindeki DisplayObjectAssemblyInfo object sınıfını extent ediyor bunu metodun ilk parametresindeki this'ten sonra yazılan obje türünden anlıyoruz (this object obj). Square metodu ise int sınıfını extend ediyor.

Extension metod tanımı yaptıktan sonra extend edilen tipin metodları arasına bu extension metod gelir.

Yukardaki resimde görüldüğü gibi DisplayObjectAssemblyInfo artık obje tipindeki nesnelerin bir metodu olarak görünüyor.

Resimde görüldüğü gibi extension metodları sınıfın kendi metodlarından ayırmak için Visual Studio unique bir ikon ile gösteriyor extension metodları ve açıklamasında da (extension) diye belirtiyor.

    class Program
    {
        static void Main(string[] args)
        {
            string test = "Test Message";

            //string object tipinden türediği için
            //DisplayObjectAssemblyInfo extension metodunu
            //kullanabiliriz.
            test.DisplayObjectAssemblyInfo();

            //int tipi için tanimladığımız
            //Square extension metodunu kullaniyoruz
            int i = 22;
            int square = i.Square();
        }
    }

Gördüğünüz gibi artık tanımladığımız extension metodları kullanabiliyoruz.

Interface'lere extension metodlar yazılabilir. Örneğin aşağıdaki gibi bir yazım yapılabilir:

        public static void ShowItems(
               this System.Collections.IEnumerable iterator)
        {
            foreach (var item in iterator)
            {
                Console.WriteLine(item);
            }
        }

Bu extension metodun ardından artık IEnumerable interface'ini implement eden Array,List<T> gibi collection nesneleri  ShowItems metodunu kullanabilir hale geldi.

Örnek Proje:
ExtensionMethodsExample.rar

C# Operator Overloading Kullanımı

Tüm programlama dillerinde olduğu gibi C#'ta da belli operatörler ile kolaylıkla işlemler yapılabilir. Örneğin + ile int tipindeki 2 elemanı matematiksel olarak toplayabiliriz.

    int a = 100;
    int b = 240;
    int c = a + b; // c artik 340

Görüldüğü gibi a ve b + operatörü ile matematiksel olarak toplanıp c değişkenine atandı. Peki a ve b int değilde string tipinde olsaydı?

    string a= "Hello";
    string b= " world!";
    string c = a + b; // c artik "Hello world!"

Buradan da görüldüğü gibi artık + operatörü matematiksel toplam yerine stringleri birleştirmek için kullanıldı. Yani her tip için operatörler farklı anlamlara gelebiliyor. Peki biz kendi yazdığımız tipler için +,- gibi operatörlere anlamlar kazandırabilir miyiz? Evet, Operator Overloading ile bunu yapabiliriz. Şimdi bunu nasıl gerçekleştirebiliriz bir örnek üzerinden bakalım. Point isminde bir sınıfımız olsun bu sınıf bir noktanın X,Y kordinatlarını tutyor olsun.

    public class Point
    {
        public int X { get; set; }
        public int Y { get; set; }
        public Point(int xPos, int yPos)
        {
            X = xPos;
            Y = yPos;
        }
        public override string ToString()
        {
            return string.Format("[{0}, {1}]", this.X, this.Y);
        }
    }

Şimdi Point sınıfımızdan oluşturduğumuz p1 ve p2 nesne örneklerini + operatörü ile toplamaya çalışalım ne sonuç çıkacak?

    class Program
    {
        static void Main(string[] args)
        {
            Point p1 = new Point(10,3);
            Point p2 = new Point(7, 4);

            //p1+p2 toplamak istiyoruz
            Point p3 = p1 + p2;

            Console.WriteLine(p3);
        }
    }

Bu durumda hata alacağız. Çünkü Point sınıfımız için tanımlanmış bir + operatörü yok.

Operator '+' cannot be applied to operands of type 'OperatorOverloadingExample.Point' and 'OperatorOverloadingExample.Point'

Şimdi operatör tanımlarımız ile Point sınıfımızı yeniden tasarlayalım:

    public class Point
    {
        public int X { get; set; }
        public int Y { get; set; }
        public Point(int xPos, int yPos)
        {
            X = xPos;
            Y = yPos;
        }
        public override string ToString()
        {
            return string.Format("[{0}, {1}]", this.X, this.Y);
        }

        //+ operatörü overload edildi
        //artık + ile 2 point nesnesinin X,Y değerleri toplanıp
        //yeni bir Point nesnesi döndürüyor
        public static Point operator + (Point p1, Point p2)
        {
            return new Point(p1.X + p2.Y, p1.Y + p2.X);
        }

        //+ operatörü overload edildi
        //artık + ile 2 point nesnesinin X,Y değerleri çıkarılıp
        //yeni bir Point nesnesi döndürüyor
        public static Point operator - (Point p1, Point p2)
        {
            return new Point(p1.X- p2.X, p1.Y -p2.Y);
        }
    }

Gördüğünüz gibi artık + ve - operatörlerini Point sınıfımız için tanımladık artık programımız hata almadan çalışaktır. + operatörü artık gönderilen 2 point nesnesinin X ve Y koordinatlarını toplayıp yeni bir Point nesnesi döndürüyor. - operatörü ise gönderilen 2 point nesnesinin X ve Y koordinatlarını çıkartıp yeni bir Point nesnesi döndürüyor.

Bu durumda programımızın çıktısı şöyle olacaktır:


Farklı olarak şu şekilde operatörler ekleyebiliriz sınıfımıza:

        // artik Point p2 = p1+5; şeklinde tanımlama yapılabilir.        
        public static Point operator +(Point p1, int change)
        {
            return new Point(p1.X + change, p1.Y + change);
        }
        //artik ++p1 şeklinde tanımlama yapilabilir.
        public static Point operator ++(Point p1)
        {
            return new Point(p1.X + 1, p1.Y + 1);
        }
        //ÖNEMLİ "=" operatörü bu şekilde override edilir
        public override bool Equals(object o)
        {
            return o.ToString() == this.ToString();
        }

        //artik p1==p2 kontrolu yapilabilir
        public static bool operator ==(Point p1, Point p2)
        {
            return p1.Equals(p2);
        }
        //artik p1!=p2 kontrolu yapilabilir
        public static bool operator !=(Point p1, Point p2)
        {
            return !p1.Equals(p2);
        }
        
Peki -=, += gibi operatörlerini kullanmak için ne yapmamız lazım? Bunlar için ekstra bişi yapmamıza gerek yok bu operatörleri + ve - operatörlerini tanımladıysak kullanabiliriz.

Şimdi birde <,> büyük küçük kontrollerini nasıl override edebiliriz ona bakalım:

    
   public class Point : IComparable<Point>
    {
        //..
        public int CompareTo(Point other)
        {
            if (this.X > other.X && this.Y > other.Y)
                return 1;
            if (this.X < other.X && this.Y < other.Y)
                return -1;
            else
                return 0;
        }
        public static bool operator <(Point p1, Point p2)
        { return (p1.CompareTo(p2) < 0); }
        public static bool operator >(Point p1, Point p2)
        { return (p1.CompareTo(p2) > 0); }
        public static bool operator <=(Point p1, Point p2)
        { return (p1.CompareTo(p2) <= 0); }
        public static bool operator >=(Point p1, Point p2)
        { return (p1.CompareTo(p2) >= 0); }
    }

Override Edilebilen Operatörler:

Unary Operators : +, -,! , ~, ++, --, true, false
Binary Operators : +, -, *, /, %, &, |, ^, <<, >>
Comparison Operators : ==,!=, <, >, <=, >=

Override Edilemeyen Operatörler:

The [] Operator : []
The () Operator :()

Örnek Proje:
OperatorOverloadingExample.rar

C# Indexer Methods Kullanımı

C#'da standart array yapısında index operatorünün [] ile kullanıldığını biliyorsunuz. Örnek kullanım olarak dizinin n. index'indeki elemanını dizi[n] diye çekebiliyoruz.

        static void Main(string[] args)
        {
            int[] myInts = { 10, 9, 100, 432, 9874 };

            //myInts dizisi üzerinde [n] index operetoru
            //kullanilarak elemanlarina ulasiliyor
            for (int j = 0; j < myInts.Length; j++)
                Console.WriteLine("Index {0} = {1} ", j, myInts[j]);
        }

Indexer Metodlar bu index yapısını kendi custom class ve structures yapılarımızda kullanabilmemizi sağlar. Örnek olarak Person nesnelerini tutan PersonCollection isminde bir sınıf oluşturalım. Burada ihtiyacımız olan [] index operatörü ile PersonCollection içerisindeki Person nesnelerine ulaşmak. Bu ihtiyacımızı koda dökersek şöyle bir şey olmalı:

    public class Person
    {
        public string Name { get; set; }
        public string Surname { get; set; }
        public int Age { get; set; }

        public override string ToString()
        {
            return string.Format("Name: {0} Surname: {1} Age: {2}"
                , Name, Surname, Age);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            PersonCollection myPeople = new PersonCollection();

            //indexer operator ile yeni person 
            //nesneleri ekleyebiliyor olmaliyiz 
            myPeople[0] = new Person() 
                { Name = "Safak", Surname = "Unel", Age = 26 };

            //for ile index üzerinden gezebiliyor olmaliyiz
            for (int i = 0; i < myPeople.Count; i++)
            {
                Console.WriteLine(myPeople[i]);
            }

            Console.ReadLine();
        }

    }

Gördüğünüz gibi PersonCollection nesnesini standart bir collection gibi indexer ile kullanmak istiyoruz. Bunun için PersonClollection classımızı şöyle tasarlamış olmalıyız:

    public class PersonCollection : IEnumerable
    {
        private ArrayList arPeople = new ArrayList();
 
        //ÖNEMLİ!! bu sayede PersonCollection'ı
        //index operatoru [] ile kullanabilecegiz
        public Person this[int index]
        {
            get { return (Person)arPeople[index]; }
            set { arPeople.Insert(index, value); }
        }

        public int Count
        {
            get { return arPeople.Count; }
        }

        public IEnumerator GetEnumerator()
        {
            return arPeople.GetEnumerator();
        }
    }

public Person this[int index] satırı ile bu sınıfımıza indexer özelliği kazandırmış olduk. Artık PersonCollection'ı index operatoru [] ile kullanabilecegiz. Her index PersonCollection içerisindeki arrPeople ArrayList'inin Person nesnelerini döndürür.

Index'in int değeri yerine kolon ismi gibi string değerler ile index operatorü kullanarak collection içerisinden veri çekebiliyoruz. Peki bunu programımıza eklemek istesek nasıl yaparız. Demek istediğim myPeople[0] yerine myPeople["Safak"] dediğimde collectionımız içerisindeki Safak isimli person nesnesini döndürmesini istiyoruz. Bunun için artık arPeople'ı ArrayList yerine Dictionary<TKey,TValue> tanımlamamız gerekir.

    class PersonDictionaryCollection:IEnumerable
    {
        private Dictionary<string, Person> arPeople 
                   = new Dictionary<string, Person>();
       //ÖNEMLİ!! artık isim üzerinden index
      //yapiliyor
        public Person this[string name]
        {
            get { return (Person)arPeople[name]; }
            set { arPeople[name] = value; }
        }

        public int Count
        {
            get { return arPeople.Count; }
        }

        public IEnumerator GetEnumerator()
        {
            return arPeople.GetEnumerator();
        }
    }

Artık şu şekilde kullanabiliriz:

        static void Main(string[] args)
        {
            PersonDictionaryCollection myPeople 
                     = new PersonDictionaryCollection();

            //indexer operator ile yeni person 
            //nesneleri ekleyebiliyor olmaliyiz 
            myPeople["Safak"] = new Person() 
                { Name = "Safak", Surname = "Unel", Age = 26 };

            //isim ile indexledigimiz icin isimden
            //Safak nesnesini buluyor
            Console.WriteLine(myPeople["Safak"]);

            Console.ReadLine();
        }

Çıktı:

Örnek Proje:
IndexerMethodsExample.rar

C# Lambda Expressions Kullanımı

C# Anonymous Methods (İsimsiz Metodlar) Kullanımı yazımızda isimsiz metodların kullanımından bahsetmiştik. Aslında Lambda Expressions isimsiz metod yaratmanın kısa yoludur.

Lambda Expressions'ı generic List<T> sınıfının FindAll() metodu üzerinden incelemeye çalışalım.
 
     // Method of the System.Collections.Generic.List<T> class.
     public List<T> FindAll(Predicate<T> match)

Yukarıda FindAll metodunun freamwork içerisindeki tanımlamasını görüyorsunuz. Gördüğünüz gibi FindAll Predicate<T> delegelesini parametre alıyor. Predicate<T> delegesi bool döndüren ve bir parametre alan metodları saklayan generic bir parametredir. Freamwork içerisindeki tanımı şu şekildedir:

    // This delegate is used by FindAll() method
    // to extract out the subset.
    public delegate bool Predicate<T>(T obj);

FindAll metodunu çalıştırdığımızda listemizin içerisindeki elemanlar sırasıyla Predicate<T> delegesinin tuttuğu metodlara gidecek ve geriye true yada false değeri dönecektir.

Önce klasik delege tanımı ile bir liste içerisindeki çift sayıları bulan programımızı yazalım:

    class Program
    {
        static void Main(string[] args)
        {
            TraditionalDelegateSyntax();
        }

        static void TraditionalDelegateSyntax()
        {
            List<int> numbers = new List<int>();
            numbers.AddRange(new int[] { 5, 7, 9, 14, 17, 2, 6 });

            //klasik delege tanimi ile
            Predicate<int> predicate = new Predicate<int>(IsEvenNumber);
            List<int> evenNumbers = numbers.FindAll(predicate);

            foreach (int item in evenNumbers)
            {
                Console.WriteLine(item);
            }

            Console.ReadLine();
        }

        // Target for the Predicate<> delegate.
        static bool IsEvenNumber(int i)
        {
            // Is it an even number?
            return (i % 2) == 0;
        }
    }

Gördüğünüz gibi önce Predicate<int> tipinde bir delege nesnesi tanımladık. Ardından IsEvenNumber metodunu bu delegenin saklayacağı metod olarak parametre verdik. Ardından FindAll metodu ile bu delegenin taşıdığı IsEvenNumber metodunu number listemizin her adımı için çalıştırdık. Klasik yöntem ile bu şekilde FindAll metodunu çalıştırabiliriz. Şimdi birde aynı örneği Anonymous Methods ile yazalım:

        static void AnonymousMethodsDelegateSyntax()
        {
            List<int> numbers = new List<int>();
            numbers.AddRange(new int[] { 5, 7, 9, 14, 17, 2, 6 });

            //Anonymous Methods ile
            List<int> evenNumbers = numbers.FindAll(
                delegate(int i){ return (i % 2) == 0; }
                );

            foreach (int item in evenNumbers)
            {
                Console.WriteLine(item);
            }

            Console.ReadLine();
        }

Bu şekilde isimsiz metodlar ile çift sayı bulma işlemini gerçekleştirmiş olduk. İsimsiz metodlar ile ilgili detaylı bilgiye şu yazıdan ulaşabilirsiniz: C# Anonymous Methods (İsimsiz Metodlar) Kullanımı 

Son olarak Lambda Expressions ile yazımına bakalım:

        static void LambdaExpressionsSyntax()
        {
            List<int> numbers = new List<int>();
            numbers.AddRange(new int[] { 5, 7, 9, 14, 17, 2, 6 });

            //Lambda Expressions ile
            List<int> evenNumbers = numbers.FindAll(i => i % 2 == 0);

            foreach (int item in evenNumbers)
            {
                Console.WriteLine(item);
            }

            Console.ReadLine();
        }

Gördüğünüz gibi Lambda Expressions ile yazımı çok basit bir hale geldik. Compiler Lambda Expressions gördüğünde o satırı Anonymous Methods'a çevirir ve öyle yorumlar. Örneğin yukarıdaki örneği ildasm exe ile açarsanız Lambda Expressions içeren satırının Anonymous Methods'a çevrildiğini görürsünüz:

      List<int> evenNumbers = list.FindAll(delegate (int i)
      {
          return (i % 2) == 0;
      });

Lambda Expressions ile yazdığımız kod şu an sadece return işlemi yapıyor. Fakat biz birden fazla işlemi burada yaptırabiliriz:

      List<int> evenNumbers = list.FindAll((i) =>
      {
          Console.WriteLine("value of i is currently: {0}", i);
          bool isEven = ((i % 2) == 0);
          return isEven;
      });

Eğer hiç bir parametre gönderilmiyor ise metoda şu şekilde Lambda Expressions yazılır:

       VerySimpleDelegate d = new VerySimpleDelegate( 
       () => {return "Enjoy your string!";} );

Eğer birden fazla parametre gönderiliyor ise:

       VerySimpleDelegate d = new VerySimpleDelegate( 
       (msg, result) => 
         {Console.WriteLine("Message: {0}, Result: {1}", msg, result);} );

Çıktı:



Örnek Proje:

LambdaExpressionsExample.rar

C# Anonymous Methods (İsimsiz Metodlar) Kullanımı

Önceki C# Delegate Kullanımı I - Delege Nedir? ve C# Delegate Kullanımı II - Generic Delegeler yazılarımızda delege kavramından bahsetmiştik. Bu yazılarımızda delege tanımlarını daha önce tanımlanmış olduğumuz named metodlar ile yaptık. C# 2.0 ile birlikte bunu isimsiz metodlar ile yapabiliyor hale geldik.

button1.Click += delegate
{
    MessageBox.Show("isimsiz metodumuz calisti");
};

Önce daha önceki yazılarımızda named metodlar ile yazdığımız delege örneğimizi hatırlayalım:

    public class Person
    {
        public delegate void PersonChangeHandler(Person person
                    , string fieldName, string newValue);
        public event PersonChangeHandler PersonHasChanged;

        public int PersonId { get; set; }
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                //eger method set edildi ise
                if (PersonHasChanged != null)
                    PersonHasChanged(this, "Name", value);
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Person p1 = new Person() { PersonId = 1, Name = "Safak" };
            p1.PersonHasChanged += ShowChangedUser;
            p1.PersonHasChanged += SendChangedUserInformation;
            p1.Name = "Test";

            Console.ReadLine();

        }

        static void ShowChangedUser(Person p, string fieldName
            , string newValue)
        {
            Console.WriteLine(@"Person ID: {0} Field: {1} 
                    New Value: {2}", p.PersonId, fieldName, newValue);
        }

        static void SendChangedUserInformation(Person p, string fieldName
            , string newValue)
        {
            Console.WriteLine(@"Person ID: {0} Bilgilendirme maili 
                    gonderiliyor", p.PersonId, fieldName, newValue);
        }
    }

Şimdi bunu anonymous methodlar ile nasıl yaparız ona bakalım:

    public class Person
    {
        public delegate void PersonChangeHandler(Person person
                    , string fieldName, string newValue);
        public event PersonChangeHandler PersonHasChanged;

        public int PersonId { get; set; }
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                //eger method set edildi ise
                if (PersonHasChanged != null)
                    PersonHasChanged(this, "Name", value);
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Person p1 = new Person() { PersonId = 1, Name = "Safak" };

            //ÖNEMLİ!! ShowChangedUser yerine isimsiz metod kullandik
            p1.PersonHasChanged += delegate(Person p, string fieldName
            , string newValue)
            {
                Console.WriteLine(@"Person ID: {0} Field: {1}   
                    New Value: {2}", p.PersonId, fieldName, newValue);
            };

            //ÖNEMLİ!! SendChangedUserInformation yerine isimsiz metod kullandik
            p1.PersonHasChanged += delegate(Person p, string fieldName
            , string newValue)
            {
                Console.WriteLine(@"Person ID: {0} Bilgilendirme maili   
                    gonderiliyor", p.PersonId, fieldName, newValue);
            };

            //ÖNEMLİ!! parametresiz isimsiz metodda kullanabiliriz
            p1.PersonHasChanged += delegate
            {
                Console.WriteLine("Degisiklik calisti.");
            };

            p1.Name = "Test";

            Console.ReadLine();

        }
    }

Gördüğünüz gibi artık ShowChangedUser ve  SendChangedUserInformation named metodlarını kullanmadık onun yerine isimsiz metodlar ile delege tanımlarımızı gerçekleştirdik.

Burda önemli noktalardan biri en son metod atama işleminde parametresiz metod ataması yapabildik. Named metod kullansaydık buna izin verilmiyor olurdu. Çünkü named metod kullanımında metodun parametreleri ile delege tanımındaki parametrelerin uyuşması gerekmektedir.

Çıktı:




İsimsiz metodlar kod aşamasında isimsiz gözüksede derlenme aşamasında, unique olarak isimlendirilmiş bir metoda dönüştürülür. ildasm ile yazdığınız koda bakarsanız bunu açıkça görebilirsiniz.

Anonymous Methodlar:

* Anonymous methodlar, delegate sözcüğüyle başlamak zorundadır.
* Herhangi bir türde parametre kabul ettiklerini ederler. ref ve out sözcükleriyle birlikte de kullanılabilir.
* params sözcüğü, anonymous metodlarda kullanılamaz.
* Tanımlandığı sınıf içerisindeki herhangi bir değişken ile aynı isme sahip değişkeni olamaz.
* Bir metoda parametre olarak gönderilebilir. Örnek:

        int[] CiftSayilar = Array.FindAll(Sayilar, delegate(int Sayi)
        {
           return (Sayi % 2 == 0);
        });

Örnek Proje:
AnonymousMethodsExample.rar

C# Event Kullanımı II - Generic Events

C# Event Kullanımı yazımızda event kavramına giriş yapmıştık. Bu yazımızda generic event kavramını incelemeye çalışacağız.

EventArgs sınıfından türeyen kendi custon EventArgs sınıfımızı yazabiliriz:

    public class CustomEventArgs : EventArgs
    {
        public int PersonId { get; set; }
        public string FieldName { get; set; }
        public string NewValue { get; set; }
    }


CustomEventArgs sınıfımızı yazdıktan sonra artık delege tanımı yapmadan freamwork içerisinde event handle etmek için kullanılan EventHandle<> generic sınıfını kullanarak programımızı yazabiliriz.

    class Person
    {
        public event EventHandler<CustomEventArgs> PersonHasChanged;

        public int PersonId { get; set; }
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                //eger method set edildi ise
                if (PersonHasChanged != null)
                {
                    CustomEventArgs c = new CustomEventArgs();
                    c.PersonId = this.PersonId;
                    c.FieldName = "Name";
                    c.NewValue = value;
                    PersonHasChanged(this, c);
                }
            }
        }

    }
    public class CustomEventArgs : EventArgs
    {
        public int PersonId { get; set; }
        public string FieldName { get; set; }
        public string NewValue { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Person p1 = new Person() { PersonId = 1, Name = "Safak" };
            p1.PersonHasChanged += ShowChangedUser;
            p1.PersonHasChanged += SendChangedUserInformation;
            p1.Name = "Test";

            Console.ReadLine();

        }

        static void ShowChangedUser(object sender, CustomEventArgs e)
        {
            Console.WriteLine(@"Person ID: {0} Field: {1} 
                    New Value: {2}", e.PersonId, e.FieldName, e.NewValue);
        }

        static void SendChangedUserInformation(object sender
                , CustomEventArgs e)
        {
            Console.WriteLine(@"Person ID: {0} Bilgilendirme maili 
                    gonderiliyor", e.PersonId, e.FieldName, e.NewValue);
        }
    }

Çıktı:


C# Event Kullanımı I - Event Nedir?

Önceki C# Delegate Kullanımı I - Delege Nedir? ve C# Delegate Kullanımı II - Generic Delegeler yazılarımızda delege kavramından bahsetmiştik. Kısacası delegeleri metot saklamak için kullandığımızı hatırlayalım.

Bu yazıları okuduysanız ya da delegeler hakkında bilginiz var ise programlarımızda delege kullanacaksak bir çok tanımlama yapmamız gerekiyor: delege tanımlama, encapsule için custom register ve unregister metotları gibi.

Şimdi düşünelim ki Person isminde bir sınıfımız var ve biz Person nesnesi üzerinde bir değişiklik yapılınca bir takım metodların çalışmasını istiyoruz. Bu durumda şöyle bir tasarım gerçekleştirebiliriz: bir delege tanımlarız ardından bu delegenin tutacağı metodları setlemek için bir metod yazarız ve bu sayede bir değişiklik olduğunda istediğimiz metodların çalışmasını sağlarız. Gördüğünüz gibi event kullanmadan tasarımı gerçekleştirdik. Şimdi bunu koda dökelim:

    public class Person
    {
        public delegate void PersonChangeHandler(Person person
           , string fieldName
           , string newValue);
        private PersonChangeHandler PersonHasChanged;

        public void RegisterMethod(PersonChangeHandler methodToAdd)
        {
            PersonHasChanged += methodToAdd;
        }

        public void UnregisterRegisterMethod(PersonChangeHandler methodToAdd)
        {
            PersonHasChanged -= methodToAdd;
        }

        public int PersonId { get; set; }
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                //eger method set edildi ise
                if(PersonHasChanged != null)
                    PersonHasChanged(this, "Name", value);
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Person p1 = new Person() { PersonId = 1, Name = "Safak" };
            p1.RegisterMethods(ShowChangedUser);
            p1.RegisterMethods(SendChangedUserInformation);
            p1.Name = "Test";

            Console.ReadLine();
        }

        static void ShowChangedUser(Person p, string fieldName
            , string newValue)
        {
            Console.WriteLine(@"\nPerson ID: {0} Field: {1} 
                    New Value: {2}", p.PersonId, fieldName, newValue);
        }

        static void SendChangedUserInformation(Person p, string fieldName
            , string newValue)
        {
            Console.WriteLine(@"Person ID: {0} Bilgilendirme maili 
                    gonderiliyor", p.PersonId, fieldName, newValue);
        }
    }

Çıktı:



Gördüğünüz gibi PersonChangeHandler isminde bir delege tanımladık. Daha sonra Person sınıfımızın içerisine PersonHasChanged isminde delege nesne örneği tanımladık. Ardından custom register ve unregister metodlarımızı yazdık. Burada PersonHasChanged delege nesne örneğinin private tanımlanması önemli çünkü bununla encapsule işlemini gerçekleştirmiş olduk. Client delege nesne örneğine direk ulaşamıyor ancak register ve unregister metodları ile delegeye yeni metodlar ekleyip çıkarabiliyor.

Peki PersonHasChanged public olsaydı ne olurdu? Bu durumda artık custom register ve unregister metodları yazmamıza gerek kalmazdı. Yeni programımız şu şekilde olurdu:

    public class Person
    {
        public delegate void PersonChangeHandler(Person person
           , string fieldName
           , string newValue);
        public PersonChangeHandler PersonHasChanged;

        public int PersonId { get; set; }
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                //eger method set edildi ise
                if(PersonHasChanged != null)
                    PersonHasChanged(this, "Name", value);
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Person p1 = new Person() { PersonId = 1, Name = "Safak" };
            p1.PersonHasChanged += ShowChangedUser;
            p1.PersonHasChanged += SendChangedUserInformation;
            p1.Name = "Test";

            Console.ReadLine();
        }

        static void ShowChangedUser(Person p, string fieldName
            , string newValue)
        {
            Console.WriteLine(@"\nPerson ID: {0} Field: {1} 
                    New Value: {2}", p.PersonId, fieldName, newValue);
        }

        static void SendChangedUserInformation(Person p, string fieldName
            , string newValue)
        {
            Console.WriteLine(@"Person ID: {0} Bilgilendirme maili 
                    gonderiliyor", p.PersonId, fieldName, newValue);
        }
    }

Bu şekilde de programımız çalışacak ve aynı sonucu üretecektir. Fakat burada bir sorun var. Encapsule işlemi gerçekleştirmedik. Client direk olarak delege nesnesine ulaşabiliyor. Bu sayede artık clientin istediği gibi delegenin Invoke metodunu çalıştırma özgürlüğü var. Örneğin aşağıdaki kodu artık yazabilir:

            p1.PersonHasChanged.Invoke(p1, "hebele", "hübele");

Bu durumda çıktı:



Gördüğünüz gibi client gitti p1 nesnemizin sanki hebele diye bir alanı varmış gibi bunun değiştiğini belirtti ve metodların çalışmasına neden oldu. Bu önemli bir güvenlik açığı oldu programımız için. Peki ne yapabiliriz?

Event kavramı tam burada ihtiyacımıza koşuyor. Eventlar, delegelerin encapsule edilmiş halidir. PersonHasChanged delege örneğini event olarak tanımlasaydık hem custom register ve unregister metodlarını yazmaktan kurtulurduk hem de yukarıda anlattığımız public encapsule problemini yaşamazdık.

Şimdi programımızı birde event ile yazalım:

public class Person
    {
        public delegate void PersonChangeHandler(Person person
                    , string fieldName, string newValue);
        public event PersonChangeHandler PersonHasChanged;

        public int PersonId { get; set; }
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                //eger method set edildi ise
                if (PersonHasChanged != null)
                    PersonHasChanged(this, "Name", value);
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Person p1 = new Person() { PersonId = 1, Name = "Safak" };
            p1.PersonHasChanged += ShowChangedUser;
            p1.PersonHasChanged += SendChangedUserInformation;
            p1.Name = "Test";

            Console.ReadLine();

        }

        static void ShowChangedUser(Person p, string fieldName
            , string newValue)
        {
            Console.WriteLine(@"Person ID: {0} Field: {1} 
                    New Value: {2}", p.PersonId, fieldName, newValue);
        }

        static void SendChangedUserInformation(Person p, string fieldName
            , string newValue)
        {
            Console.WriteLine(@"Person ID: {0} Bilgilendirme maili 
                    gonderiliyor", p.PersonId, fieldName, newValue);
        }
    }

Gördüğünüz gibi hem custom register ve unregister işlemlerinden kurtulduk hem de bu sayede event ile delegemiz encapsule olduğu için client Invoke metodunu kafasına göre çalıştıramadı.

p1.PersonHasChanged'ın sonuna nokta koyup metot listesine erişmeye çalışırsanız hiç bir metodun çıkmadığını görürsünüz.

C# windows form uygulaması geliştirdiyseniz burada eventların bolca kullanıldığını görürsünüz. Buton click event'ı gibi bir çok event bulunmakta. Konuyu daha iyi anlamak için bu eventları incelemenizi öneririm.

Örnek Projeler:
PublicDelegateProblem.rar
EventDelegateExample.rar




C# Delegate Kullanımı II - Generic Delegeler

C# Delegate Kullanımı I - Delege Nedir? yazımızda delege kavramını, ne işe yaradığını, nerelerde kullanıldığını incelemiştik.

C# generic delege tanımlama yapmamıza olanak sağlar. Örneğin, void döndüren ve bir parametre alan 2 metodumuz olsun: StringTarget; string tipinde parametre alıyor ve geriye void döndürüyor. IntTarget ise int tipinde parametre alıyor ve o da geriye void döndürüyor.

Normal şartlarda 2 delege tanımlamamız birine string parametresi diğerine int parametresi yazmamız gerekir. Şimdi ilk aklımıza gelen şekilde bu programı yazalım:

        delegate void MyStringDelegate(string args);
        delegate void MyIntDelegate(int args);

        static void Main(string[] args)
        {
            MyStringDelegate stringDelegate = new MyStringDelegate(StringTarget);
            MyIntDelegate intDelegate = new MyIntDelegate(IntTarget);

            stringDelegate("Safak");
            intDelegate(26);

            Console.ReadLine();
        }

        static void StringTarget(string arg)
        {
            Console.WriteLine("arg in uppercase is: {0}", arg.ToUpper());
        }
        static void IntTarget(int arg)
        {
            Console.WriteLine("++arg is: {0}", ++arg);
        }

Gördüğünüz gibi string tipinde parametre alan metodumuz için MyStringDelegate, int tipinde parametre alan metodumuz için ise MyIntDelegate delegesini tanımladık. Peki yine void döndüren fakat parametre olarak double alan bir metodu delege etmek istesek ne yapacağız? Bu durumda yeni bir MyDoubleDelegate yazmamız gerekecek. Bu durumu ortadan kaldırmak için şimdi bu programı birde generic delege kullanarak yazalım:

 
        //generic delegate
        delegate void MyGenericDelegate<T>(T args);

        static void Main(string[] args)
        {
            //generic delegate
            MyGenericDelegate<string> stringDelegate 
                = new MyGenericDelegate<string>(StringTarget);
            MyGenericDelegate<int> intDelegate 
                = new MyGenericDelegate<int>(IntTarget);

            stringDelegate("Safak");
            intDelegate(26);

            Console.ReadLine();
        }

        static void StringTarget(string arg)
        {
            Console.WriteLine("arg in uppercase is: {0}", arg.ToUpper());
        }
        static void IntTarget(int arg)
        {
            Console.WriteLine("++arg is: {0}", ++arg);
        }

Gördüğünüz gibi delegate void MyGenericDelegate<T>(T args); generic delege tanımı yaparak Hem StringTarget hemde IntTarget metodlarını saklayabildik. Bundan sonra double paramere alan geriye void döndüren bir fonksiyon yazsakta yeni bir delege tanımı yapmamıza gerek kalmayacak, MyGenericDelegate üzerinden nesne örneği oluşturup T yerine double vererek bunu gerçekleştirebiliriz.

Çıktı:



Action<> ve Func<> Kullanımı:

Normal şartlarda delege kullanmamız için şu adımları gerçekleştirmemiz gerekiyor:
* İçinde saklayacağı metot formatına uygun custom delege tanımı yapmak
* Bu custom delegeden bir nesne örneği oluşturmak
* Oluşturduğumuz nesne üzerinden Invoke işlemini gerçekleştirmek
Action<> ve Func<> kısa bir şekilde delege tanımı yapmamıza olanak sağlıyor.

Action<> Kullanımı:

Geriye void döndüren metodları tutan delegeleri kısa yoldan tanımlamamızı sağlar.

        static void Main(string[] args)
        {
            Action<string, int> action
                      = new Action<string, int>(DisplayAge);
            action("Safak", 26);
        }

        static void DisplayAge(string fullName, int age)
        {
            Console.WriteLine(fullName + " : " + age);
        }

Gördüğünüz gibi delege tanımı yapmadan DisplayAge metodu saklandı ve Invoke edilebildi. Bir kez daha söylemekte fayda var Action<> geri değer döndürmez.

Func<> Kullanımı:

Action<> 'ın geri değer döndürebilen halidir. Son parametre dönüş değer tipini belirtir. Örnek kullanım:

        static void Main(string[] args)
        {
            Func<DateTime, double> func 
                 = new Func<DateTime, double>(GetUserAgeAtTime);
            double dateDif = func(DateTime.Now.AddYears(-2));
            Console.WriteLine("\nDate Dif: {0}", dateDif);

            Console.ReadLine();
        }

        static double GetUserAgeAtTime(DateTime birthDate)
        {
            return (DateTime.Now - birthDate).TotalDays;
        }

Yukarıdaki örnekte <DateTime, double> şeklinde yazılmıştır. DateTime parametrenin tipini double ise dönüş değer tipini belirtir.

Örnek ProjelerGenericDelegateExample.rar ,  ActionFuncExample.rar

C# Delegate Kullanımı I - Delege Nedir?

C# dilinde delegelerin kullanım amacı metot saklamaktır. Delegeler Olay(event) tabanlı programlama ve asenkron programlama yaparken, anonim metot yazarken kullanılır. Bir diğer kullanım amacı da, bir metoda parametre olarak başka bir metot verebilmektir.

Delegate tanımlanırken şu öğeler belirtilmelidir:
* Saklanacak metot
* Eğer dönüş değeri varsa dönüş değeri
* Eğer saklanacak metodun parametreleri varsa bu parametreler

Örnek bir delege tanımı:

    delegate int MyDelegate(int x);
    MyDelegate temsilci = new MyDelegate(CalisacakMetot);

Yukarıda örnek bir delege tanımı yaptık. İlk satırda yazdığımız "delegate int" delege içerisinde saklayacağımız metodun int tipinde dönüş değeri olacağını gösteriyor. "(int x)" ise saklayacağımız metodumuzun int tipinde değer alacağını gösteriyor. Alt satıra geçersek ise "CalisacakMetot" delegemizin saklayacağı metot ismidir. Kısacası bu delege tanımı; programımızın içinde şu şekilde tanımladığımız "int CalisacakMetot(int x)" metodunu gerektiğinde çağırmak için saklıyor.

Şimdi örnek bir delege uygulaması yapalım:

    delegate int MathOperation(int x, int y);

    public class SimpleMath
    {
        public static int Add(int x, int y)
        { 
            int retVal = x +y;
            Console.WriteLine("Add : " + retVal);
            return retVal;
        }
        public static int Subtract(int x, int y)
        {
            int retVal = x - y;
            Console.WriteLine("Subtract : " + retVal);
            return retVal;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MathOperation mathOp = new MathOperation(SimpleMath.Add);
            mathOp(5,3);

            Console.ReadKey();
        }
    }

Burada "delegate int MathOperation(int x, int y);" satırıyla basit bir delege tanımı yaptık. Tanımı yaparken saklayacağı metodların dönüş tiplerinin int olması gerektiğini ve bu metodların int tipinde 2 parametre alması gerektiğini belirttik.

Daha sonra ise "MathOperation mathOp = new MathOperation(SimpleMath.Add)" bu satır ile mathOp delege nesnesi oluşturduk. Nesne örneği oluşturulurken construtor üzerinden bu delege nesnesine SimpleMath.Add metodunu saklaması için ekledik. Artık delegemiz bu metodu saklar hale geldi.

En canalıcı kısma geçersekte "mathOp(5,3)" bu şekilde delege nesnemiz üzerinden sakladığı metot olan SimpleMath.Add metoduna 5 ve 3 değerlerini verip bu metodu çalıştırdık.

Program çalıştığında çıktı 5+3'ten "Add : 8" şeklinde olacaktır.

Çıktı:



Bir delege birden fazla metot saklayabilir. "+=" ile delegeye metot ekleyebiliriz. Eklediğimiz metodlar FIFO mantığıyla ilk eklenen metoddan başlayıp sen eklenen metoda doğru sırayla çalıştırılır.

    mathOp += SimpleMath.Subtract;

programımıza bu satırı eklediğimizde artık önce Add metodu ardından Subtract metodu çalışacaktır. Şimdi çıktımız şu şekilde olacaktır.



Metod ekleyebildiğimiz gibi "-=" ile saklanan metodları silebilirizde:

    mathOp -= SimpleMath.Add;

Bu kod satırının ardından artık Add metodu çalışmıyacaktır. Çıktı artık şu şekilde olcaktır:



Bir delege tanımı yapılınca arka planda System.MulticastDelegate'den türetilmiş sealed bir class oluşturulur.

ildasm toolu ile exe'mizin içerisine bakarsanız bizim MathOperation delegemizin için de sealed bir class oluşturulmuş olduğunu görürsünüz.



Burada dikkatinizi BeginInvode, EndInvoke ve Invoke metodlarına çekmek istiyorum.

Invoke: Saklanan metodları senkron şekilde çalıştırır. Örneğimize dönersek "mathOp(5,3)" dediğimizde aslında "mathOp.Invoke(5,3)" metodu çalışıyor ve saklanan metodlarımız Invoke metodu yardımıyla sırayla çalıştırılıyor.

BeginInvoke ve EndInvoke ise asenkron işlemlerde kullanılıyor fakat bu konuya ileriki yazılarımızda incelemeye çalışacağız.

GetInvocationList: Delegemizin sakladığı metot listesini döndürür. Örnek kullanımı şu şekildedir:

            // delegenin sakladigi metodlar listeleniyor
            foreach (Delegate d in mathOp.GetInvocationList())
            {
                Console.WriteLine("Method Name: {0}", d.Method);
                //SimpleMath static oldugu icin bos gelecektir
                Console.WriteLine("Type Name: {0}", d.Target);
            }

Örnek Proje: DelegateExample.rar

C# Generic ObservableCollection<T> Kullanımı

ObservableCollection<T> yapımız System.Collections.ObjectModel namespace'i altında bulunuyor. İçerisinde aynen List<T> gibi T ile belirttiğimiz tipte elemanlar tutar. List<T> collection yapısından en önemli farkı ise içerisinde CollectionChanged isminde bir event barındırmasıdır. Collection nesnemiz değiştiğinde bu event fire olur ve biz bu sayede collection nesnemiz üzerindeki değişikliklerden haberdar oluruz.

Şimdi örnek bir program ile collection yapımızı inceleyelim:

 public class Person
    {
        public string Name { get; set; }
        public string Surname { get; set; }
        public int Age { get; set; }

        public override string ToString()
        {
            return "Fullname: " + Name + " " + Surname + " Age: " + Age;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ObservableCollection<Person> people 
                = new ObservableCollection<Person>();

            // event'i delege ediyoruz
            people.CollectionChanged += people_CollectionChanged;

            people.Add(new Person() 
                { Name = "Salih", Surname = "Sayar", Age = 42 });
            people.Add(new Person() 
                { Name = "Ahmet", Surname = "Yilmaz", Age = 21 });

            people.RemoveAt(0);

            Console.ReadLine();
        }

        private static void people_CollectionChanged(object sender,
  System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            // Ne nedenle bu event calisti?
            Console.WriteLine("nAction for this event: {0}", e.Action);

            // Silindi ise
            if (e.Action == 
   System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
            {
                Console.WriteLine("Silinen insanlar:");
                foreach (Person p in e.OldItems)
                {
                    Console.WriteLine(p.ToString());
                }
                Console.WriteLine();
            }

            // eklendi ise
            if (e.Action == 
    System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                Console.WriteLine("Eklenen insanlar:");
                foreach (Person p in e.NewItems)
                {
                    Console.WriteLine(p.ToString());
                }
            }
        }
    }

Çıktı:



Örnek Proje: ObservableCollectionExample.rar

C# Generic SortedSet<T> Kullanımı

SortedSet<T> yapısı C#'ın generic collection yapılarından biridir. T ile belirttiğimiz tipte elemanlar tutar. C# 4.0 ile hayatımıza girmiş yapılardan biridir. Bu yapının en önemli özelliği listeyi sıralı tutmasıdır. Siz yeni bir eleman eklesenizde silsenizde liste yine sıralı olacaktır.

SortedSet ile int, string gibi tipler tutacaksak bu tipler IComparer interface'ini implemente ettiği için ekstra hiç bir şey yapmamıza gerek yoktur. Bu elemenları SortedSet'e ekleriz o arkada bunları sıralar. Peki Person isminde bir sınıf tutacaksak bunu nasıl sıralıyacak?

Bunun için kendi Comparer sınıfımızı yazmamış gerekir. Şimdi gelin bu durumu kod üzerinde inceleyelim:

 public class Person
    {
        public string Name { get; set; }
        public string Surname { get; set; }
        public int Age { get; set; }

        public override string ToString()
        {
            return "Fullname: " + Name + " " + Surname + " Age: " + Age;
        }
    }

    public class PersonComparer : IComparer<Person>
    {
        public int Compare(Person x, Person y)
        {
            // 1: siralamada daha onde
            //-1: siralamada daha geride
            // 0: ayni sirada
            if (x.Age > y.Age)
                return 1;
            else if (x.Age < y.Age)
                return -1;
            else
                return 0;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //PersonComparer sinifi ile liste yasa gore sirali
            //hale getiriliyor.
            SortedSet<Person> personSortedSet 
                = new SortedSet<Person>(new PersonComparer());

            //SortedSet yapisina Person nesnelerini ekliyoruz
            personSortedSet.Add(new Person() 
                { Name = "Salih", Surname = "Sayar", Age = 42 });
            personSortedSet.Add(new Person() 
                { Name = "Ahmet", Surname = "Yilmaz", Age = 21 });
            personSortedSet.Add(new Person() 
                { Name = "Safak", Surname = "Unel", Age = 26 });

            // 3 person yasina gore sirali ekrana yaziliyor
            foreach (Person p in personSortedSet)
            {
                Console.WriteLine(p);
            }
            Console.WriteLine();

            // 2 kisi daha ekleniyor
            personSortedSet.Add(new Person() 
                { Name = "Sunay", Surname = "Tarik", Age = 23 });
            personSortedSet.Add(new Person() 
                { Name = "Eren", Surname = "Sahan", Age = 34 });

            // yeni eklenen 2 person nesnesi sirali sekilde
            // eklenecektir.
            foreach (Person p in personSortedSet)
            {
                Console.WriteLine(p);
            }

            Console.ReadLine();
        }
    }

Çıktı:


Örnek Proje: GenericSortedSetExample.rar

C# Generic Queue<T> Kullanımı

Queue<T> yapısı C#'ın generic collection yapılarından biridir. T ile belirttiğimiz tipte elemanlar tutar ve FIFO (ilk giren ilk çıkar) mantığıyla elemanları dışarı çıkartır.

Bu yapıya örnek olarak bankaya gittiğimizde girdiğimiz sırayı verebiliriz. İlk bankaya gidene ilk sıra gelir gibi.

Enqueue : Queue yapımıza yeni bir eleman eklemek için kullanılır.
Dequeue : Queue yapımızdan sıradaki ilk elemanı alıp bu elemanı siler.
Peek : Queue'deki nesneyi silmeden son elemanı döndürür.

Şimdi örnek bir Queue yapısı geliştirelim:

 public class Person
    {
        public string Name { get; set; }
        public string Surname { get; set; }
        public int Age { get; set; }

        public override string ToString()
        {
            return "Fullname: " + Name + " " + Surname + " Age: " + Age;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Queue<Person> personQueue = new Queue<Person>();

            //Queue yapisina Person nesnelerini ekliyoruz
            personQueue.Enqueue(new Person() 
                 { Name = "Ahmet", Surname = "Yilmaz", Age = 21 });
            personQueue.Enqueue(new Person() 
                 { Name = "Safak", Surname = "Unel", Age = 26 });
            personQueue.Enqueue(new Person() 
                 { Name = "Salih", Surname = "Sayar", Age = 42 });

            //Peeked: son eleman silmeden queue'deki son elemana bakiliyor
            //Dequeue: ilk eleman cekilip siliniyor  
            Console.WriteLine("Peeked : {0}", personQueue.Peek());
            Console.WriteLine("Dequeue : {0}", personQueue.Dequeue());

            Console.WriteLine("nPeeked : {0}", personQueue.Peek());
            Console.WriteLine("Dequeue : {0}", personQueue.Dequeue());

            Console.WriteLine("nPeeked : {0}", personQueue.Peek());
            Console.WriteLine("Dequeue : {0}", personQueue.Dequeue());

            try
            {
                // eger stack'imiz bosalttiktan sora Peek veya Pop  
                // metodlarimizi cagirirsak hata aliriz  
                Console.WriteLine("nPeeked : {0}", personQueue.Peek());
                Console.WriteLine("Dequeue: {0}", personQueue.Dequeue());
            }
            catch (Exception e)
            {
                Console.WriteLine("nError: {0}", e.Message);
            }

            Console.ReadLine();  

        }
    }

Çıktı:



Örnek Proje: GenericQueueExample.rar

C# Generic Stack<T> Kullanımı

Stack<T> yapısı C#'ın generic collection yapılarından biridir. T ile belirttiğimiz tipte elemanlar tutar ve LIFO (Son giren ilk çıkar) mantığıyla elemanları dışarı çıkartır.

Push : Collection nesnemize yeni eleman ekler.
Pop : Collection'a son giren elemanı çekip bu elemanı collection'dan siler.
Peek : Collection'daki nesneyi silmeden son elemanı döndürür.

Şimdi örnek bir stack yapısı geliştirelim:

       public class Person
       {
           public string Name { get; set; }
           public string Surname { get; set; }
           public int Age { get; set; }

           public override string ToString()
           {
              return Name + " " + Surname + " Age: " + Age;
           }
        }
        static void Main(string[] args)
        {
            Stack<Person> personStack = new Stack<Person>();

            personStack.Push(new Person() 
                 { Name = "Ahmet", Surname = "Yilmaz", Age = 21 });
            personStack.Push(new Person() 
                 { Name = "Safak", Surname = "Unel", Age = 26 });
            personStack.Push(new Person() 
                 { Name = "Salih", Surname = "Sayar", Age = 42 });

            //Peeked: son eleman silmeden stack'teki son elemana bakiliyor
            //Popped: son eleman cekilip siliniyor
            Console.WriteLine("Peeked : {0}", personStack.Peek());
            Console.WriteLine("Popped : {0}", personStack.Pop());

            Console.WriteLine("\nPeeked : {0}", personStack.Peek());
            Console.WriteLine("Popped: {0}", personStack.Pop());

            Console.WriteLine("\nPeeked : {0}", personStack.Peek());
            Console.WriteLine("Popped: {0}", personStack.Pop());

            try
            {
                // eger stack'imiz bosalttiktan sora Peek veya Pop
                // metodlarimizi cagirirsak hata aliriz
                Console.WriteLine("\nPeeked : {0}", personStack.Peek());
                Console.WriteLine("Popped: {0}", personStack.Pop());
            }
            catch (Exception e)
            {
                Console.WriteLine("\nError: {0}", e.Message);
            }

            Console.ReadLine();
        }
    }

Çıktı:



Örnek Proje : GenericStackExample.rar

C# Generic Liste-Sınıf Yapıları

Soldaki kupa fotografını generic konusunu araştırırken buldum çok hoşuma gittiği için sizle paylaşmak istedim. Aslında fotograf tüm konuyu özetliyor T kupamızın içerisine koyacağımız çay, kahve, su gibi değişkenleri simgeliyor. Bizde T'ye bunlardan birini verip kupamızı dolduruyoruz. Yazının soununda fotografa dönün sizde çok seveceksiniz:)

Asıl konumuza dönersek neden generic sınıf ve listelere ihtiyaç duyuyoruz bunu anlamak için önce non-generic üyeler kullanarak bir kaç örnek yapalım bakalım ne takım problemler ortaya çıkacak görelim.

Önce hepimizin defalarca kullandığı dizileri düşünelim: Dizi tanımı yapılırken dizi hangi tipte değerler taşıyacak ise bunu belirtmemiz gerekiyor; dizimiz string tipinde değerler tutacak ise string, int tipinde değerler tutacaksa int tipinde dizi oluşturulur. Daha sonrada dizimize boyut vermemiz gerekir. Bunları örnek bir string dizisi tanımlayarak gösterelim:

static void Main(string[] args)
{
   // string tipinde 3 elemanli bir dizi olusturalim
   string[] strArray = {"Ahmet", "Mehmet", "Ali" };
}

 Gördüğünüz gibi dizimizin boyutunu 3 verdik bu yüzden bu dizi artık 3 değer tutar 4. değeri artık bu diziye atayamayız. Atarsak hata alırız. Peki bizim ne kadarlık bir diziye ihtiyaç duyduğumuz önceden belli değilse ne olacak? Ya çok büyük bir dizi tanımlayacağız ama bu da belekte boşa yer kaplayacaktır ya da tahmin ettiğimizden daha fazla kayıt gelince programımız patlayacaktır.

Bu durumu ortadan kaldırmak için System.Collection namespace'i altında bulunan ArrayList gibi collection sınıflarını kullanabiliriz. Bu collection'lara önceden boyut belirtmek zorunda değiliz. Add metodu ile istediğimiz sayıda obje saklayabiliriz.

static void Main(string[] args)
{
    ArrayList strArray = new ArrayList();
    strArray.Add("Ahmet");
    strArray.Add("Mehmet");
    strArray.Add("Ali");
    strArray.Add("Ayşe");
}

Gördüğünüz gibi bir boyut vermeden istediğimiz sayıda obje gönderebildik. Peki aynı ArrayList'e tüm gönderdiğimiz değerler string tipinde olmasına rağmen 5. obje olarak int tipinde bir obje gönderebilir miyiz?
  strArray.Add(1);
İşte aslında neden generic listelere ihtiyaç duyuyoruz bu sorunun cevabında gizli. Evet gönderebiliriz. Çünkü ArrayList object tipinde nesneler tutar. string, int, class tüm bunlar .net dünyasında bir object olduğu için ArrayList'te hepsi aynı anda tutulabilir. Peki bunun dezavantajları nelerdir? Listeleyelim:

* ArrayList'e bir eleman eklediğimizde bu eleman object tipinde değilse ArrayList object tipinde elemanlar tuttuğu için arkaplanda önce bu eleman objeye çevrilir öyle ArayList'e eklenir. Bu performans sorununa neden olacaktır. 
* Tersi içinde aynı durum söz konusudur. ArrayList'ten bir eleman çektiğimizde object tipinde bize dönecektir. Fakat biz bu elamanı int tipinde istiyorsak bunu int'e çevirmemiz gerekecektir. Aynı şekilde buda performans sorununa neden olacaktır. 
* foreach ile ArrayList'imiz üzerinde döndüğümüzü düşünelim bize gelen her nesne object olduğu için gerçek halinin ne olduğunu bilemeyiz. Gelen her elemanı int tipine çevirsek belki arada bir string tipinde "Ayşe" gelecektir bu durumda programımız hata alacaktır. Yani non-generic koleksiyonlar type-safe değildir.

Peki bu tip durumlardan kurtulmak için ne kullanmamız gerekiyor: Generic List<T>

// Bu List<T> artık sadece int tipinde elemanlar tutar
List intListesi = new List();
intListesi.Add(10);
intListesi.Add(2);

Bu durumda artık listeye int tipinde bir eleman eklenirken arka planda obje tipine çevrilmesine gerek kalmadı. Aynı şekilde generic listemizden bir eleman çektiğimizde bunun int tipinde olacağından eminiz, fazladan bir convert işlemi yapmamızda gerek kalmadı. Ayrıca dönecek elemanın int tipinde olacağı kesin olduğu için type-safe bir yapıya sahiptir.

Bunun gibi List<T>Stack<T>Queue<T>SortedSet<T> gibi generic liste yapıları mevcuttur bu yapılarla ilgili örnekleri ilerleyen yazılarımızda paylaşacağız. Ayrıca ilginç bir collection yapısı olan ObservableCollection<T>'da mevcuttur incelemenizi öneririm.

Bunların dışında .Net Freamwork'ü içerisinde bir çok generic interface, sınıf , metot ve liste bulunmaktadır. Bizde kendi generic interfacesınıf ve metodlarımızı yazabiliriz. Örnek bir metot yazarak bunu açıklayalım:

//int tipinde Swap
static void Swap(ref int a, ref int b)
{
   int temp;
   temp = a;
   a = b;
   b = temp;
}

Görüldüğü gibi gönderilen 2 int değerini birbirine atayan basit bir Swap metodu yazdık. Fakat düşünün ki daha sonra string tipindeki 2 elemanı birbirine atayacak bir metoda daha ihtiyaç duyduk. Bu durumda bir metot daha yazmamız gerekecek:

//string tipinde Swap
static void Swap(ref string a, ref string b)
{
   string temp;
   temp = a;
   a = b;
   b = temp;
}

Bunun gibi oluşturduğumuz Person sınıfındaki 2 nesneyi birbirine atamamız gerekebilir. Bu tip durumlarda sınıfımıza hep yeni bir metot yazmak zorunda kalıcaz. Bunun yerine generic bir metot yazabiliriz:

static void Swap<T>(ref T a, ref T b)
{
   T temp;
   temp = a;
   a = b;
   b = temp;
}

Şu şekilde artık tek metot üzerinden Swap işlemimizi yapabiliriz:

//int ile Swap
int a = 1;
int b = 2;
Swap<int>(ref a, ref b);

//string ile Swap
string s1 = "Ahmet";
string s2 = "Mehmet";
Swap<string>(ref s1, ref s2);

Şimdi birde generic bir sınıf tasarlayalım:

        public class Point<T>
        {
            // noktamizin x,y koordinatlari
            private T xPos;
            private T yPos;
            // constructor.
            public Point(T xVal, T yVal)
            {
                xPos = xVal;
                yPos = yVal;
            }
            // Generic properties.
            public T X
            {
                get { return xPos; }
                set { xPos = value; }
            }
            public T Y
            {
                get { return yPos; }
                set { yPos = value; }
            }
            public void ResetPoint()
            {
                //default T tipinin default degerini dondurur
                //T int olursa 0, string olursa null gibi
                X = default(T);
                Y = default(T);
            }
        }

Gördüğünüz gibi Point sınıfı tanımladık ve bu Point sınıfının içerisine ise X,Y koordinatları tanımladık. Fakat generic bir sınıf tanımı yaptığımız için bu koordinatların tipini önceden tanımlamadık. Bu sınıfı kullanan client isterse int tipinden Point nesnesi oluşuturur bu durumda X,Y kordinatları int tipinden olur; isterse de double tipinden oluşturur bu durumda da X,Y koordinatları double tipinden olur.

    // int ile Point
    Point<int> p = new Point<int>(10, 10);
    // double ile Point
    Point<double> p2 = new Point<double>(5.4, 3.3);

Burada default keywordu dikkatinizi çekmiş olabilir: default keyword'u kod commenti olarak yazdığımız gibi tipin default değerini döndürür. int için 0, string için null gibi.

where T : <ifade> Kullanımı:

Generic yapısını kurarken T için bazı kısıtlamalar koyabiliriz. Bunun için "where T : <ifade>" yapısı kullanılır:

where T : struct  = T'nin System.ValueType tipinde olması gerektiğini belirtmek için kullanılır.
where T : class   = T'nin System.ValueType tipinde olmaması gerektiğini belirtmek için kullanılır.
where T : new()  = T'nin default constructor'ının olması gerektiğini belirtmek için kullanılır.
where T : NameOfBaseClass = NameOfBaseClass yerine bir sınıf ismi yazılır ve T'nin bundan türemiş olması gerektiğini belirtmek için kullanılır.
where T : NameOfInterface = NameOfInterface yerine interface ismi yazılır ve T'nin bu interface'i veya interfaceleri implemente etmiş olması gerekir. Birden fazla interface yazılabilir.
Örnek kullanımına bakarsak:

public class MyGenericClass<T> where T : class, IDrawable, new()

Umarım yararlı olmuştur. Görüşmek üzere.

C# IComperable, IComparer Ara Yüzleri ve Kullanımı

IComparable interface'i uygulandığı sınıfa CompareTo adlı bir metot kazandırır ve bu metot geriye int tipinde bir değer döndürür. IComparable interface'ini sınıfımıza uygulayarak sınıfımızdan türeyen nesneleri karşılaştırılabilir hale getiririz ve böylece sıralama, karşılaştırma vb. işlemleri yapabiliriz.

// This interface allows an object to specify its
// relationship between other like objects.
public interface IComparable
{
  int CompareTo(object o);
}

Örnek olarak bir integer dizimiz olsun bunu Array sınıfının static Sort metodu ile sıralayalım:

int[] sayilar = { 4, 7, 1, 3, 2, 6, 5, 8 };
Array.Sort(sayilar);
 
foreach (int sayi in sayilar)
{
        Console.WriteLine(sayi);
}

Kodumuzu çalıştırdığımızda hatasız bir şekilde sıralı listeyi elde ederiz.

Peki bu sefer bir Car sınıfı oluşturalım ve ardından bir car dizisi oluşturup bu diziyi sıralamaya çalışalım bakalım ne olacak:

class Car
    {
        public int CarId { get; set; }
        public string Brand { get; set; }
        public string Model { get; set; }
    }
class Program
    {
        static void Main(string[] args)
        {
            Car[] carList = new Car[3];
            carList[0] = new Car() 
                { CarId = 3, Brand = "Opel", Model = "Astra" };
            carList[1] = new Car() 
                { CarId = 1, Brand = "Opel", Model = "Corsa" };
            carList[2] = new Car() 
                { CarId = 2, Brand = "Opel", Model = "Insignia" };

            Array.Sort(carList);

            foreach (Car item in carList)
            {
                Console.WriteLine("CarID: {0}, Brand: {1}, Model: {2}"
                    , item.CarId, item.Brand, item.Model);
            }
        }
    }

Bu kodu çalıştırdığımızda aşağıdaki hatayı alırız:
“Failed to compare two elements in the array.”
Çünkü dizimizi sort metoduna gönderdik fakat bu nesnenin hangi kurallara göre karşılaştırılacağını belirtmedik. Peki ilk örneğimizde de int tipinin nasıl karşılaştıracağını belirtmemiştik onda neden hata almadık? Çünkü Int32 sınıfı  IComperable interface'ini implemente etmektedir bu yüzden sayılar aritmetik sırayla sıralanmıştır. O zaman kodumuzun çalışması için bizimde Car sınıfının IComperable interface'ini implemente etmesini sağlamamız gerekiyor. Şimdi buna göre Car sınıfını yeniden yazalım :

class Car: IComparable
    {
        public int CarId { get; set; }
        public string Brand { get; set; }
        public string Model { get; set; }

        public int CompareTo(object obj)
        {
            Car temp = obj as Car;
            if (temp != null)
            {
                if (this.CarId > temp.CarId)
                    return 1;
                if (this.CarId < temp.CarId)
                    return -1;
                else
                    return 0;
            }
            else
                throw new ArgumentException("Car nesnesi degil!");
        }
    }

CompareTo Dönüş Değerlerinin Anlamları:

0'dan küçük ise : nesneden önce geliyor
0 ise : o anki nesne
0'dan büyük ise: nesneden sonra geliyor

Yukarıdaki logic'e uygun CarId property'sine göre sıralama yapan bir CompareTo metodu yazdık. Artık Array.Sort metoduna dizimizi gönderirsek CarId'ye göre sıralama yapacaktır.


Bir adım daha ileri gidelim ve düşünün ki bazen nesnenizi CarId'ye göre bazen Brand ismine göre bazende Model ismine göre sıralamanız gerekiyor. Bu durumda ne yapabiliriz? Array sınıfının static sort metodu ilk parametre olarak dizi ikinci parametre olarakta IComparer tipinde değişken alabiliyor. Yani kendi custom comparer'larımızı yazarsak bunu gerçekleştirebiliriz. Yazdığımız custom comperarımız IComparer interface'ini implemente etmesi gerekiyor ve bu implementasyon Compare metodunu sınıfımıza ekliyor. Şimdi örnek olarak Model ismine göre sıralayan comparer'ımızı yazalım:

class CarModelComparer : IComparer
    {
        public int Compare(object x, object y)
        {
            Car t1 = x as Car;
            Car t2 = y as Car;
            if (t1 != null && t2 != null)
                return String.Compare(t1.Model, t2.Model);
            else
                throw new ArgumentException("Car nesnesi degil!");
        }
    }

Bunun ardından aşağıdaki gibi sort işlemini gerçekleştirirsek:

            //Model ismine gore siralaniyor
            Array.Sort(carList, new CarModelComparer());

            foreach (Car item in carList)
            {
                Console.WriteLine("CarID: {0}, Brand: {1}, Model: {2}"
                    , item.CarId, item.Brand, item.Model);
            }

Artık foreach ile döndüğümüzde nesne örnekleri Model ismi sırasına göre gelecektir. Bu şekildeki iterasyon sonrası çıktı şu şekilde olacaktır:


İstersek sınıflarımıza IComparer tipinde property ekleyerek sınıfımıza değişik biçimlerde sort işlemi yaptıracak özellikler kazandırabiliriz. new şeklinde comparer oluşturmak yerine bunu kullanmak hem kullanım kolaylığı sağlayacaktır hemde son kullanıcı oluşturduğu nesneyi nelere göre sıralayabilecek bunu görecektir.

        public static IComparer SortByModel
        { get { return (IComparer)new CarModelComparer(); } }

        Array.Sort(carList, Car.SortByModel);

Örnek Proje : IComparableExample.rar