Bu yazıya başlamadan önce, eğer okumadıysanız aşağıda yer alan, C# 3.0 ile ilgili önceden yazdığım yazıları okumanızı tavsiye ederim.

C# 3.0 ile Tanışalım - 1 : C# 3.0 Ne Getiriyor ve LINQ Projesi Nedir?
C# 3.0 ile Tanışalım - 2 : Bilinçsizce Türlendirilmiş Lokal Değişken (Implicitly Typed Local Variable) : var
C# 3.0 ile Tanışalım - 3 : Nesne ve Koleksiyonlara Ilk Değer Atayıcı (Object and Collection Initializer)

Bilindiği gibi bir tip (sınıf, yapı, numaralandırıcı, arayüz ya da temsilci) tanımlanıp bir .NET assembly'sine derlendikten sonra eldeki *.exe ya da *.dll, son çıktıdır. Yeni üyeler eklemek, var olan üyeleri değiştirmek, bir üye silmek için yeniden kodlamak ve güncel .NET assembly'sini elde etmek için derlemek gerekir (ya da System.Reflection.Emit isim alanındaki tipleri kullanarak dinamik assembly elde etmek gibi biraz daha zorlayıcı yollara başvurulabilir).

Bu istek, artık C# 3.0 genişletme metotları tanımlayarak gerçekleştirilebilmektedir. Genişletme metotları (extension methods), var olan bir tipi doğrudan güncellemeden, o tipe yeni metotlar eklemeyi sağlayan bir özellik olarak özetlenebilir. Projeye eklenmiş harici bir assembly'ye, projede kod olarak yer alan bir tipe ya da .NET temel sınıf kütüphanesinde yer alan bir tipe sonradan metot eklenebilir. Çalışılan tip, ihtiyaç duyulan bir fonksiyonelliği sağlamıyorsa, bu yöntemin kullanılması önerilebilir. Genişletme metotları aynı zamanda bir tipi, belli bir üye grubunu desteklemeye zorlama ihtiyacı varsa da faydalı olabilir (Polimorfik yapı benzeri); ancak orjinal tip tanımlaması değiştirilemez. Kısacası genişletme metotları, önceden derlenmiş tiplere sanki kendi metoduymuş gibi kullanılmak üzere yeni fonksiyonellik sunmalarını sağlamaktadır.

Genişletme metotlarının esas çıkış hedefi LINQ projesidir. Var olan .NET tiplerine (IEnumerable ve IEnumerabe<T> arayüzünü uygulamış olan .NET tipleri), LINQ sorguları yapılmasını sağlamak için C# 3.0 geliştiricileri çok fazla sayıda genişletme metodu yazmışlardır. LINQ standart sorgu operatörleri (LINQ standard query operators), bunlara en güzel örneklerdir. Bu operatörleri kullanmak için System.Linq isim alanına ihtiyaç vardır. Ardından IEnumerable<T> arayüzünü uygulamış herhangi bir tipin OrderBy, GroupBy, Max gibi nesne metotlarına sahip olduğu görülebilir. Standart sorgu operatörleri metotlarının listesini görmek için IEnumerable<T> arayüzünden türeyen bir tipin nesnesi üzerinden nokta (.) koymak yeterlidir.

Not: IEnumerable<T> olarak ele alınabileceklere örnek olarak List<T> generic koleksiyonu, herangi bir dizi ya da bir LINQ sorgu sonucunu taşıyan IEnumerable<T> değişkeni gösterilebilir.

         

Aşağıdaki örnek, standart sorgu operatörlerinden Max ve OrderBy metotlarının, bir double dizisi üzerinden nasıl çağrılacağını göstermektedir:

//Min()
var oranlar = new[] { 4.5 , 2.3 , 22.8 , 4.75 , 12.56 };
double enBuyuk = oranlar.Max();
Console.WriteLine("oranlar dizisindeki en büyük sayi : {0}",enBuyuk);

//OrderBy()
var sonuc = oranlar.OrderBy(g => g);
foreach (var i in sonuc)
{
    Console.Write(i + " - ");
}

Yukarıdaki kodun çıktıları aşağıdaki gibidir:

      22.8
      2.3 - 4.5 - 4.75 - 12.56 - 22.8

Not: OrderBy() genişletme metoduna aktarılan parametredeki => , lambda deyimlerinde kullanılır. C# 3.0 ile gelen bu yeni özellik, temsilci yazımına yeni bir soluk getirmektedir. İlerleyen yazılardan birisi bu konu ile alakalı olacaktır.


Genişletme Metotları Ile Çalışmak

Genişletme metotları tanımlanırken dikkat edilmesi gereken bazı kurallar vardır:

 - Genişletme metodu statik bir sınıf içerisinde tanımlanır. Sınıf adının ne olduğu önemli değildir. Sınıfın erişim belirleyicisi ise (internal ya da public), bu genişletme metodunu kullananın erişebileceği şekilde belirlenmelidir.

 - Ayrıca metodun kendisi de statik olarak işaretlenmelidir. Metoda verilen isim, eklendiği sınıfın üyesi olarak otomatik tamamlamada (intellisense) çıkacak olan isimdir. Metodun erişim belirleyicisi, en azından sınıfının erişim belirleyicisi düzeyinde olmalıdır.

 - Yazılan metodun hangi sınıfa ait olacağı parametre listesinde bildirilir. Metot, hangi tipe eklenmek isteniyorsa, o tipte bir parametre alınır ve önüne this anahtar kelimesi koyulur.

 - Genişletme metotlarını yazdıktan sonra çağırabilmek için öncelikle yazıldığı sınıfın isim alanı eklenmelidir. Yukarıdaki Max ve OrderBy standart sorgu operatörlerinin kullanılabilmesi için öncelikle aşağıdaki isim alanının using bloklarına eklenmesi gerekir.

      using System.Linq

 - Genişletme metotları yazıldıktan sonra iki şekilde çağrılabilir. Birincisi, ait olduğu sınıfın nesnesi üzerinden olabilir. Bu, amaca daha uygun bir çağırma şeklidir. İkincisi ise tanımlandığı statik sınıf üzerinden olabilir. Metot çağrılırken, yazılan ilk parametre verilmez; çünkü o parametre metodun hangi tipin üyesi olacağını derleyiciye bildirmek için arka planda kullanılır. Çağıran kişi değer vermeye 2.parametreden başlar.


Genişletme Metotları Tanımlamak

Şimdi örnek üzerinde konuyu biraz daha açık hale getirmeye çalışalım :

GenisletmeSinifi isimli, iki tane genişletme metoduna sahip bir sınıf olsun. Birinci metot, .Net temel sınıf kütüphanesinde yer alan System.String sınıfına yeni bir fonksiyonellik eklesin. Bu fonksiyonellik, string tipinde değişkenler üzerinden çağrılacak TersiniVer() isimli metot tarafından sağlansın. Adından anlaşılacağı gibi bir metin verinin tersini geriye döndüren bu metodun, değişkenin kendisi dışında herhangi bir parametre almasına gerek yoktur. Zaten bu parametreyi de nesne kullanıcısı görmez.
İkinci genişletme metodunun adı ise Parcala olsun. Parcala metodu, çağrıldığı dizinin belli bir bölümünü geri döndürme fonksiyonelliğine sahip olsun. Bu metot, herhangi bir dizi üzerinden çağrılabilsin ve ayrıca 2 tane parametre alsın. Alınan ilk parametre,  dizideki kaçıncı indeksli elemandan parçalamaya başlanacağını, ikinci parametre ise kaç elemanın istendiğini belirlesin. Yani 10 elemanlı bir dizi üzerinden çağrılacak Parcala<>() metoduna parametre olarak 4 ve 3 değerleri aktarıldığında metot geriye aynı tipte yeni bir dizi döndürür. Yeni dizi, orjinal dizinin 4. , 5. ve 6. elemanlarından oluşan 3 elemanlı bir dizi olur. Bu genişletme metodunun, tek tip dizi için değil de her tipte dizi için fonksiyonellik sunmasını sağlamak adına generic mimariden faydalanılabilir:

namespace Genis
{
     public static class GenisletmeSinifim
     {
          //Bu metot, herhangi bir string tipli degisken uzerinden cagrilabilir.
          public static string TersiniVer(this string veri)
          {
                 string sonuc = string.Empty;
                 for (int i = veri.Length - 1; i >= 0; i--)
                 {
                         sonuc += veri[i].ToString();
                 }

                 return sonuc;

          }

          //Bu metot, generic olarak tasarlandigi icin, herhangi bir tipteki dizi nesnesi uzerinden erisilebilir ve cagrilabilir.
          public static T[] Parcala<T>(this T[] kaynakDizi, int baslangicIndeksi, int elemanSayisi)
          {
                 if (baslangicIndeksi < 0 || elemanSayisi < 0 || kaynakDizi.Length - baslangicIndeksi < elemanSayisi)
                 {
                         throw new Exception("Parametre kombinasyonunda bir sorun var...");
                 }

                 T[] yeniDizi = new T[elemanSayisi];
                 Array.Copy(kaynakDizi, baslangicIndeksi, yeniDizi, 0, elemanSayisi);
                 return yeniDizi;
          }
     }
}

Genişletme Metotlarını Kullanmak

Tekrar etmekte fayda ki bu metotların aldığı ilk parametreler zorunludur ve this ile başlar. Metodun hangi tipin nesnesi üzerinden çağrılabileceğini bildirir. O tip dışındaki herhangi bir sınıf ya da yapı üzerinden genişletme metodu çağrılırsa derleme zamanı hatası alınır. Yazılan genişletme metotları çağrılmadan önce dahil oldukları isim alanı eklenmelidir.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using Genis;

namespace CsharpUcSifir
{
     class Program
     {
            static void Main(string[] args)
            {
                     string s = "istanbul";
                     Console.WriteLine(s);
                     Console.WriteLine(s.TersiniVer());
                     ///////////////////////////////////////////////
                     var dizi = new[] { 1,2,3,4,5,6,7,8 };
                     foreach (int i in dizi.Parcala<int>(3, 4))
                     {
                           Console.Write(i + " / ");
                     }
            }
     }
}

Genişletme metotlarının kullanımı normal metot kullanımından farklı değildir. Bir metodun genişletme metodu olduğu, otomatik tamamlamada (intellisense) çıkan (extension) yazısından anlaşılır. Yazılan iki örneğin çıktıları aşağıdaki gibidir :

istanbul
lubnatsi
4 / 5 / 6 / 7


Genişletme Metotlarının Aşırı Yüklenmesi

Genişletme metotları, normal metotlar gibi aşırı yüklenebilir :

namespace Genis
{
     public enum HarfTuru { Sesli, Sessiz }

     static class GenisletmeSinifim
     {
          public static int HarfleriSay(this string veri)
          {
                return veri.Length;
          }
         
          public static int HarfleriSay(this string veri, HarfTuru tur)
          {
                veri = veri.ToLower();
                var sesliHarfler = new[] {'a', 'e','ı','i','o','ö','u','ü'};
                var sesliSayisi = 0;
                var sonuc = 0;
                for (int i = 0; i < veri.Length; i++)
                {
                       for (int j = 0; j < sesliHarfler.Length; j++)
                       {
                                 if (veri[i] == sesliHarfler[j])
                                           sesliSayisi++;
                       }
                }

               if (tur == HarfTuru.Sesli)
                       sonuc = sesliSayisi;
               else if (tur == HarfTuru.Sessiz)
                       sonuc = veri.Length - sesliSayisi;
               
               return sonuc;
          }
     }
}

HarfleriSay() isimli metodun, parametresiz versiyonunda string tipli bir metin verinin kaç harften oluştuğu Length özelliğinden kolayca hesaplanırken ikinci aşırı yüklenmiş verisyon, System.String sınıfına yeni bi fonksiyonellik kazandırmaktadır. Metot, parametre aldığı HarfTuru kullanıcı tanımlı numaralandırıcı değişkeni ile metin veri içerisindeki sesli ya da sessiz harfleri sayar. Metodun aşırı yüklenmiş haliyle kullanımı aşağıda görülmektedir :

               


Genişletme Metotlarını Statik Olarak Çağırmak

Genişletme metotlarının aldığı zorunlu ilk parametre, metodun hangi tipin nesnelerine ait olacağını bildirmektedir. Nesne üzerinden HarfleriSay() metodu çağrıldığında arka planda neler olduğunu ildasm.exe yardımıyla inceleyelim:

Yukarıdaki çıktıda görüldüğü gibi derleyici, metodu normal bir statik metot gibi çağırmakta ve ilk parametre olarak string değişkeni (yani genişletme metodu olarak eklendiği tipi) aktarmaktadır. Öyleyse yazılan genişletme metodunun aşağıdaki C# kodu ile çağrılması ile yukarıdaki gibi string değişken üzerinden çağrılması arasında CIL çıktıları açısından fark yoktur. Ancak genişletme metotlarına başvurulma amacına uygunluk açısından, nesne üzerinden çağrılması daha uygun diyebiliriz.