Delegates, Anonymous Methods, Lambda Expressions (Delegeler, Anonim/İsimsiz Metodlar, Lambda İfadeleri)

Delegates (Delegeler)

Delegeleri motodların referanslarını tutan akıllı konteynırlar olarak tanımlayabiliriz. Delegeler bir yada birden çok metodun referansını içerebilmektedir.
Delege instance’ı tarafından çağırılacak metod delegeye register edilerek kullanılmaktadır. Delegeler static metodlar yada herhangi bir class instance’ı üzerinden erişilen metodların referanslarını tutar. Bir motod delegeye register edildiğinde delege kendi iç (internal collection) koleksiyonuna method referansını ekler (delegate’s “invocation list).

Delege instance’ları referans ettikleri metodları senkron yada ansenkron olarak çağırabilmektedir.

Bir delege asenkron olarak çağırıldığında thread pool’dan ayrı bir thread tarafından çalıştırılır. Delege instance invoke (call) edildiğinde referans ettiği tüm metodlar delege tarafından atomatik olarak çağırılır.

Delegeler sadece herhangi bir metodun referansını içermez, delege tanımında tanımlı olan signature’ı (dönüş tipi ve aldığı argümanlara) aynı olan metodların referanslarını tutar.


Örnek bir delege tanımı :

public delegate void MyDelegate(string myString);

Yukarıdaki örnek delege tanımına baktığımızda gövde (body) kodları olmadan tanımlanan bir metoda benzediğini söyleyebiliriz.

Delegate signature’ı (dönüş tipi ve aldığı argümanları) referans olabileceği metodları belirlemektedir. Örnek olarak tanımladığımız MyDelegate delegesinin dönüş void yani bişey döndürmeyen string bir argüman alan metodların referansını tutabilir.

Sonuç olarak MyDelegate register olabilecek bir metod aşağıdaki gibi olacaktır.

private void MyMethod(string someString) 
{
   // method gövdesi (body)
}

Aşağıdaki metodlarda MyDelegate tarafından referasları tutulamayan metodlara örnek olabilir çünkü signature’ları (dönüş tipi ve aldığı argümanları) MyDelegate ile uyuşmamaktadır.

private string MyOtherMethod(string someString) 
{
   // method gövdesi (body)
}

private void YetAnotherMethod(string someString, int someInt) 
{
   // method gövdesi (body)
}

Yeni bir delege tipi tanımı yapıldıktan sonra, metodların invoke edilmesi için delege instance’ı oluşturulmalı ve bu instance’ın referans edeceği metodlar register edilmelidir.

// Delegate instancı oluşturup referans edilecek metod register edilmesi.
MyDelegate del = new MyDelegate(MyMethod);

Delege instance’ı oluşturulduktan sonra aynı delege bir çok metodun referansını tutabilir bunun için sadece diğer metodlar aşağıdaki gibi delege instance’ına register edilmelidir :

// Aynı delege instance'ına ikinci bir metodun referansının register edilmesi.
del += new MyDelegate(MyOtherMethod);

Bu işlemden sonrada delegeyi aşağıdaki şekilde invoke ederiz:

del("delegeye register olan tüm metodlar invoke edilsin");

Yukarıdaki gibi bir invoke işleminden sonra MyDelegate kendisine register edilmiş olan MyMethod ve MyOtherMethod metodlarını “delegeye register olan tüm metodlar invoke edilsin” string argümanını alarak çalıştıracaktır.

Neden delegate kullanmalıyız?

Eğer delegeleri ilk defa öğreniyorsanız, basitçe bir metod çağırmak varken neden delegeler ile uğraşayım bana ne gibi faydaları var diyebilirsiniz.

Buna yanıt olarak yazdığımız component’lerde belirli metodların belirli durumlarda ne zaman çağıracağını bilmememizdendir.

Önemli bir nokta ise delegeler .NET component’lerinin yazmış olduğunuz kodları çağırmasını sağlar, .Net component’leri metod signature (dönüş tipi ve aldığı argümanlar) dışında yazdığınız kod ile ilgili bişey bilmezler.

Örneğin .Net Framework component’lerinden biri olan Timer component’i çalışmak için yazmış olduğunuz koda ihtiyaç duyar. Çünkü Timer component’inin hangi metodu çağıracağını bilmesi imkansızdır, bu invoke edilecek delege tipine register olan metod ve signature’u ile belirlenmiş olur. Sonrasında Timer component’i çalıştırıldığında delegesinin instance’ına register olan signature’una uygun olan metodunuzu çalıştırmış(invoke) olur.

Yine bu aşamada Timer componenti sizin yazdığınız metod ile ilgili bir şey bilmemektedir.

Timer component’inin tüm bildigi delegedir. Delege metodunuzla ilgili detayları (dönüş tipi ve argümanları) bilmekte ve delegeye register olan metodları çalıştırmaktadır(invoke).

Sonuç itibarı ile Timer component’i yazmış olduğunuz metod ile ilgili birşey bilmemesine rağmen metodunuzu çalıştırmış oldu.

Bahsetttiğimiz Timer componenti örneği gibi yazdığımız kod içerisinde belirli bir noktada hangi metodun çağırılmasını istiyorsak delege kullanabiliriz. Belirdeğimiz bu noktada direk olarak metod çağrımı yapmak yerine delege instance’ı nı çalıştırarak (invoke) bu delege instance’ına register olmuş hangi metodlar varsa onları çalıştırabiliriz. Böylece kodumuzun içerisine direk olarak metod yazmak yerine başka bir yerdeki uyumlu metodları kullanma esnekliği sağlar.

Bu anlattıklarımıza ek olarak .Net içerisinde bize hazır olarak sunulan delegelerde bulunmaktadır ;

Predicate delegesi System.Predicate. Bu delege object tipinde tek argüman almakta ve boolean tipinde değer döndürmektedir.

Predicate delegesini bir örnek üzerinde kullanalım:

static void Main()
{
    int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 21 };
    int[] smallPrimes = Array.FindAll(primes, IsSingleDigit);   // { 2, 3, 5, 7 }
}
 
static bool IsSingleDigit(int value)
{
    return 0 <= value &amp;&amp; value <= 9;
}

Func delegesi Func. Bu delegede maximum generic 16 argüman almakta ve generic bir değer geri döndürmektedir.

Func<> generic delegesini bir örnek üzerinde kullanalım:

Func<int,int,double> divide=(x,y)=>(double)x/(double)y;
Console.WriteLine(divide(2,3));

Yukarıdaki delege tanımına baktığımızda iki tane integer tipinde argüman alıp double tipinde bölme sonucunu geri döndürdüğünü gözlemleyebiliriz. Func<> kullanımında son argüman her zaman dönüş tipini belirmektedir. Action<> ile Func<> arasındaki tek fark ise Action<> delegesinin dönüş tipi tipinin olmamasıdır.

Action delegesi Action. Bu delegede maximum generic 16 argüman almakta ve herhangi bir dönüş değeri bulunmamaktadır.

Action<double> write=(aDouble)=>Console.WriteLine(aDouble);
write(divide(2,3));

Anonymous Methods(Anonim/İsimsiz Metodlar)
.Net 2.0 ile gelen önemli geliştirmelenden biride anonim metodlardır. Anonim metodlar delegeler gibi bir metodun referansını tutmak yerine kod içerisinde metod varmış gibi kod bloğu yazmamızı sağlar. Ancak anonim metodların normal metodlardan en büyük farkı kullanıldıkları kod bloğu dışarısından erişilememeleridir.

Bir örnek üzerinde anlatacak olursak:

class Test
{
    delegate void TestDelegate(string s);
    static void M(string s)
    {
        Console.WriteLine(s);
    }

    static void Main(string[] args)
    {
        // Normal delege ile metod'a referans
        TestDelegate testDelA = new TestDelegate(M);
        
        // String argüman alan ve anonim olan metod tanımı.
        // Aşağıdaki satıra dikkat edildiğinde referans edilen herhangi bir metod yoktur. 
        // Metod varmış gibi kod yazılıp daha sonra bu metoda erişim sağlanmıştır.
        TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); };

        // delegeleri invoke edelim
        testDelA("Merhaba. Ben bir degeleyim ve M metoduna referans etmekteyim.");
        testDelB("Merhaba bende anonim metodum.");

        Console.WriteLine("Çıkmak için herhangi bir tuşa basınız.");
        Console.ReadKey();
    }
}
/* Kod çıktısı:
    Merhaba. Ben bir degeleyim ve M metoduna referans etmekteyim.
    Merhaba bende anonim metodum.
    Çıkmak için herhangi bir tuşa basınız.
 */

Lambda Expressions (Lambda İfadeleri)

.Net 3.0 ile gelen olan Lambda ifadeleri anonim metod, delege, fonksiyon tanımları ve expression tree tiplerini daha kısa bir biçimde tanımlamamıza yardımcı olan bir özelliktir. Tüm lambda ifadeleri => lambda operatörünü kullanır. Lambda operatörünün sol tarafı (eğer varsa) giriş parametlerini (argümanlarını) belirtir, sağ tarafı ise koşul yada kod bloklarını barındırır.

Expression Lambdas
Lambda ifadesinin sağ tarafında herhangi bir koşul tanımlandığında yada herhangi bir metod çağrımı yapıldığında kullanılan ifadeye expression lambda demektedir.

Expression lambda yapısı:

(input parameters) => expression

Yukarıdaki genel kullanımda anlaşılacağı üzere lambda ifadesinde giriş parametresi bir tane ise parantez kullanımını opsiyoneldir. Ancak birden fazla parametre kullanılacak ise parantez kullanılması gereklidir. Birden fazla parametre kullanıldığında parametreleri birbirinden ayırmak için aşağıdaki örnekte görüleceği üzere virgül kullanılması gerekmektedir:

(x, y) => x == y

Bazı durumlarda ise compiler lambda ifadesinde tipini belirtmediğiniz parametrelerin tiplerini belirlemede(type inference) zorluk çeker yada bu tipleri ne olduğunu belirleyemez. Bu tarz durumlarda karşılaşıldığında parametrelerin tiplerini direk (explicit) olarak tanımlamanız gerekir.

Direk olarak tipini belirttiğimiz lambda ifadesine örnek :

(int x, string s) => s.Length > x

Eğer hiç bir giriş parametresi olmayan bir metod çağırımı yapılacaksa () parantez ifadesi kullanmamız gerekir :

() => SomeMethod()

Statement Lambdas

Lambda ifadesi içerisinde { } kullanılarak birden fazla satırda işlem yapıldığı durumlara statement lambda denilmektedir.

Statement lambda yapısı:

(input parameters) => {statement;}

Lambda ifadesi içerisinde birden fazla satırda çeşitli işlemler yapılabilir; ancak önerilen 2 veya 3 satırda gerekli işlemleri bitirmektir.

delegate void TestDelegate(string s);
…
TestDelegate myDel = n => { 
                            string s = n + " " + " Dünya"; 
                            Console.WriteLine(s); 
                          };
myDel("Merhaba");

Statement lambdas, anonim metodlara benzetilebilir ancak expression tree oluşturmak için kullanılamazlar.
Expression tree’lere bir başka makalemde detaylıca değineceğim.

Standart sorgu operatorleri ile Lambda ifadeleri kullanımı

Bir çok standart sorgu (query) operatörünün giriş parametresi generic Func delegesidir. Func delegesi kullanılarak sorgu operatörlerinde kullanılacak parametreler (argümanlar), dönüş değeri ve tipleri belirlenir. Func delegeleri kullanıcı tanımlı ifadeleri sarmallamada (encapsulate) çok kullanışlı olabilmektedir, yani kaynak verinin her elemanına uyguladığınız ifadeyi çok basit bir şekilde uygulayabilmektedir :

Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // anlaşılacağı üzere geri dönüş değeri false olacaktır.

Standart bir sorgu operatörü olan Count metodu ile örnek bir sorgu :

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);

Yukarıda dikkat edileceği üzere giriş parametresinin tipi verilmemiştir, tip belirtmediğinde compiler tipi kendisi çözümleyecektir yada direk (explicit) olarak parametrenin tipini tanımlayabiliriz. Yukarıdaki lambda ifadesine baktığımızda 2 ye bölünebilen sayıları sayacaktır.

Aşağıdaki ifadeye bakıldığında 6 sayısından küçük sayıları döndüreğini anlayabiliriz ancak 9 sayısından öncekileri bize döndürecektir, çünkü belirttiğimiz koşulun gerektiği sonuç bu olacaktır :

var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);

Önemli bir kaç not

Standart sorgu ifadeleri kullanırken dikkat etmeniz gereken bir kaç ipucunu gözden kaçırmamak için burada bir kaç örnek daha vermek isterim.

List<string> list = new List<string>(); 
IQueryable<string> query = list.AsQueryable(); 
list.Add("one"); 
list.Add("two"); 
list.Add("three"); 
 
string foo = list.First(x => x.EndsWith("o")); 
string bar = query.First(x => x.EndsWith("o")); 
// buraya kadar herşey normal ancak aşağıdaki gibi bir kullanıma dikkat edilmelidir.
foo = list.First(x => { return x.EndsWith("e"); }); //herhangi bir hata ile karşılaşılmaz.
bar = query.First(x => { return x.EndsWith("e"); }); //bu ifadede hata alırsınız.
//A lambda expression with a statement body cannot be converted to an expression tree
// üst satırdaki ifade bu şekilde düzenlendiğinde hata alınmamış olur.
bar = query.First((Func<string,bool>)(x => { return x.EndsWith("e"); })); 

Kaynak;

C# Event Implementation Fundamentals, Best Practices and Conventions
C# Delegates, Anonymous Methods, and Lambda Expressions
Func Delegate
Action Delegate
Anonymous Methods (C# Programming Guide)
Lambda Expressions (C# Programming Guide)
C# Lambda Expressions

0 thoughts on “Delegates, Anonymous Methods, Lambda Expressions (Delegeler, Anonim/İsimsiz Metodlar, Lambda İfadeleri)”

Bir cevap yazın