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