Daha önceki makalemizde LINQ’in aslında, Enumarable adlı static bir sınıf içerisinde yer alan extension metotlardan oluştuğundan bahsetmiştik. Bugün ise kendi LINQ metotlarımızı nasıl yazacağımızı göreceğiz. Bu sayede uygulamalarımızda, amacımıza uygun olarak LINQ metotları tasarlayabiliriz. Daha anlaşılır olması adına konuya başlamadan önce, Extension Metot, foreach iterasyonu, yield operatörü, anonim metot, lambda operatörü ve Linq metotları gibi konular hakkında fikir sahibi olmamızın işimizi kolaylaştıracağını hatırlatmakta fayda var.

İlk olarak Urun adında bir sınıf yazacağız. Daha sonra da Dukkan adlı bir sınıfımız olacak ve bu sınıf arka tarafta bir Urun dizisi tutacak. İçerisinde iterasyon yapacağımız sınıf Dukkan sınıfı olacaktır.

Yazacağımız LINQ metotları içerisinde ihtiyaç dahilinde Urun nesnelerini karşılaştırma ihtiyacı hissedeceğimizden dolayı IComparable interface’ini implement etmemiz gerekecektir.

public class Urun : IComparable<Urun>

    {

        public int Id { get; set; }

        public string Ad { get; set; }

        public double Fiyat { get; set; }

 

        public Urun(int id, string ad, double fiyat)

        {

            Id = id;

            Ad = ad;

            Fiyat = fiyat;

        }

 

        public static bool operator >(Urun u1, Urun u2)

        {

            return u1.Fiyat > u2.Fiyat;

        }

        public static bool operator <(Urun u1, Urun u2)

        {

            return u1.Fiyat < u2.Fiyat;

        }

 

        public int CompareTo(Urun other)

        {

            if (this.Fiyat > other.Fiyat)

                return 1;

            if (this.Fiyat < other.Fiyat)

                return -1;

            else return 0;

        }

    }

Dukkan sınıfı içerisinde de iterasyon yapacağımız için, IEnumerable interface’ini impelement etmesi gerekmektedir.

public class Dukkan : IEnumerable<Urun>

    {

        Urun[] _urunSepeti;

 

        public Urun[] UrunSepeti

        {

            get { return _urunSepeti; }

            set { _urunSepeti = value; }

        }

 

        Urun[] UrunleriGetir()

        {

            return new Urun[]

            {

                new Urun(1,"Monitör",200),

                new Urun(2,"Klavye",80),

                new Urun(3,"Mouse",40),

                new Urun(4,"Laptop",1000),

                new Urun(5,"TV",650)

            };

        }

 

        public Dukkan()

        {

            UrunSepeti = UrunleriGetir();

        }

 

        public IEnumerator<Urun> GetEnumerator()

        {

            for (int i = 0; i < UrunSepeti.Length; i++)

                yield return UrunSepeti[i];

        }

 

        IEnumerator IEnumerable.GetEnumerator()

        {

            return this.GetEnumerator();

        }

    }

Where

Buraya kadarki kısım geçmişe dönük ufak bir hatırlatma oldu diyebiliriz. Şimdi LINQ metotlarımızı yazmaya başlayabiliriz. Bunun için static bir sınıf oluşturalım ve içerisine metotlarımızı ekleyelim. İlk olarak Filtrele adında bir metot yazalım, işlevi Where metodu ile aynı olsun.

public static class BizimEnumerable

    {

        public static IEnumerable<T> Filtrele<T>(this IEnumerable<T> source, Func<T, bool> predicate)

        {

            foreach (T obj in source)

            {

                if (predicate(obj))

                    yield return obj;

            }

        }

    }

Metodun aldığı parametreleri inceleyelim. this anahtar sözcüğü ile, metodun hangi tipler üzerinde çıkacağını belirtiyoruz. Generic IEnumerable interface’ini implement eden tüm sınıflar bu metodu kullanabilecekler. ikinci parametre ise T tipinden parametre alan ve geriye bool dönen bir delegedir. Bir metot parametre olarak bir delege alıyorsa, bunun anlamı, parametre olarak bir metot göndermektir. Ya bu imzaya uygun bir metot yazıp bu metodu parametre olarak göndeririz. Ya da metodu anonim olarak tanımlarız. Ancak her işlem için yeni bir metot tanımlamak yerine lambda operatörü ile ananim metot yazmak işimizi fazlası ile kolaylaştıracaktır. Metodun içerisinde ise kaynak üzerinde iterasyon yapılır ve kaynak içerisindeki her bir nesne , ikinci parametrede belirtmiş olduğumuz anonim metoda parametre olarak verilir. Metot geriye bool bir değer döndüğünden, filtereden geçen eleman yield operatörü ile geriye döndürülür.

Program sınıfı içerisinde Dukkan tipinden bir nesne örnekleyelim ve Filtrele metodunu kullanalım. Adı ‘M’ ile başlayan ürünleri filtreleyerek ekrana yazdıralım.

class Program

    {

        static void Main(string[] args)

        {

            Dukkan dukkan = new Dukkan();

 

            foreach (Urun u in dukkan.Filtrele(u => u.Ad.ToLower().StartsWith("m")))

            {

                Console.WriteLine(u.Ad);

            }

        }

    }

image

Şimdi de Where metodunun aşırı yüklenmiş bir versiyonunu yazalım, parametre olarak Func<T,int,bool> alsın. Yazacağımız anonim metot T ve int tipinden 2 parametre alarak geriye bool döndürecek. int tipinden olan parametre index’i temsil etmektedir.

public static IEnumerable<T> Filtrele<T>(this IEnumerable<T> source, Func<T, int, bool> predicate)

        {

            int index = 0;

            foreach (T obj in source)

            {

                if (predicate(obj, index))

                    yield return obj;

                index++;

            }

        }

Program tarafında testimizi yapalım. Bir integer dizisi oluşturalım. Değeri, bulunduğu index’in 10 katından küçük olan olan sayıları filtreleyelim.

int[] sayilar = { 0, 30, 20 };

 

foreach (int sayi in sayilar.Filtrele((s, i) => s <= i * 10))

{

Console.WriteLine(sayi);

}

image

Select

Görüldüğü gibi Filtrele metodu, where metodunun yaptığı işi yaparak verilen koşula göre filtreleme işlemini gerçekleştirdi. Şimdi de Select metodunu kendimiz yazalım. Metodun adı Seç olsun.

public static class BizimEnumerable

    {        

        public static IEnumerable<TResult> Sec<T, TResult>(this IEnumerable<T> source, Func<T, TResult> predicate)

        {

            foreach (T obj in source)

                yield return predicate(obj);

        }

    }

predicate delegesinin işaret ettiği metot parametre olarak T alan ve geriye TResult döndüren bir metottur. Sec metodu içerisinde kaynakta itersyon yapılır ve elde edilen her bir nesne predicate delegesinin işaret ettiği metoda verilir ve elde edilen değer geriye döndürülür. Bir ürün nesnesine karşılık adının elde edilmesi durumunu ele alalım. Burada T parametresi bir Urun iken, TResult  parametresi de bir string olur. Bir ürünün fiyatına karşılık kdv ekli fiyatını elde etmek istersek de T tipi de TResult tipi de double olur.

class Program

    {

        static void Main(string[] args)

        {

            foreach (string ad in dukkan.Sec(u => u.Ad))

            {

                Console.WriteLine(ad);

            }

 

            foreach (double kdvDahilFiyat in dukkan.Sec(u => u.Fiyat * 1.18))

            {

                Console.WriteLine(kdvDahilFiyat);

            }

 

  }

     }

image

Take

Getir adında, Take metodunun işlevine sahip bir metot tasarlayalım. Parametre olarak integer bir değer alsın ve bu parametre değeri kadar elemanı elde etmemizi sağlasın.

public static IEnumerable<T> Getir<T>(this IEnumerable<T> source, int number)

        {

            int counter = 0;

            foreach (T obj in source)

            {

                counter++;

                yield return obj;

 

                if (counter == number)

                    break;

            }

        }

dukkan içerisindeki ilk 3 ürünün adını ekrana yazdıralım.

class Program

    {

        static void Main(string[] args)

        {

            Dukkan dukkan = new Dukkan();

 

            foreach (Urun u in dukkan.Getir(3))

            {

                Console.WriteLine(u.Ad);

            }

                   }

        }

image

Count

Say adında, Count metodu ile aynı işleve sahip bir metot yazalım. Daha sonra da bu metodun bir aşırı yüklenmiş versiyonunu yazarak, içerisinde filtreleme yapılabilmesini sağlayalım.

public static class BizimEnumerable

    {       

        public static int Say<T>(this IEnumerable<T> source)

        {

            int toplam = 0;

            foreach (T obj in source)

            {

                toplam++;

            }

            return toplam;

        }

 

        public static int Say<T>(this IEnumerable<T> source, Func<T, bool> predicate)

        {

            int toplam = 0;

            foreach (T obj in source)

            {

                if (predicate(obj))

                {

                    toplam++;

                }

            }

            return toplam;

        }

    }

Yazmış olduğumuz metotları Program tarafında test edelim.

class Program

    {

        static void Main(string[] args)

        {

            Dukkan dukkan = new Dukkan();

 

            Console.WriteLine("Eleman sayısı : {0}", dukkan.Say());

 

            Console.WriteLine("***********************************************");

 

            Console.WriteLine("Fiyatı 100 liradan az olan ürün sayısı: {0}", dukkan.Say(u => u.Fiyat < 100));

        }

    }

Ekran çıktısı aşağıdaki gibi olacaktır.

image

Sum

Topla adında bir metot yazalım ve uygun tiplere göre aşırı yüklenmiş versiyonlarını ekleyelim.

public static class BizimEnumerable

    {

        public static double Topla<T>(this IEnumerable<T> source, Func<T, double> predicate)

        {

            double toplam = 0;

            foreach (T obj in source)

            {

                toplam += predicate(obj);

            }

            return toplam;

        }

 

        public static double? Topla<T>(this IEnumerable<T> source, Func<T, double?> predicate)

        {

            double? toplam = 0;

            foreach (T obj in source)

            {

                toplam += predicate(obj);

            }

            return toplam;

        }

 

        public static int Topla<T>(this IEnumerable<T> source, Func<T, int> predicate)

        {

            int toplam = 0;

            foreach (T obj in source)

            {

                toplam += predicate(obj);

            }

            return toplam;

        }

 

        public static int? Topla<T>(this IEnumerable<T> source, Func<T, int?> predicate)

        {

            int? toplam = 0;

            foreach (T obj in source)

            {

                toplam += predicate(obj);

            }

            return toplam;

        }

    }

Dukkan sınıfı içerisindeki fiyatların toplamını alalım. Alanların tipi double olduğundan dolayı, ilk yazdığımız versiyonu kullanacağız. Diğer versiyonları kullanmayacağız. Sadece nasıl olduğunu görmemiz açısından ele almak istedim.

class Program

    {

        static void Main(string[] args)

        {

            Dukkan dukkan = new Dukkan();

 

            Console.WriteLine("Kaynaktaki ürünlerin toplam fiyatı : {0}", dukkan.Topla(u => u.Fiyat));

 

        }

    }

image

Average

public static class BizimEnumerable

    {

        public static double Ortalama<T>(this IEnumerable<T> source, Func<T, int> predicate)

        {

            int toplam = 0;

 

            foreach (T obj in source)

            {

                toplam += predicate(obj);

            }

 

            return toplam / source.Say();

        }

 

        public static double Ortalama<T>(this IEnumerable<T> source, Func<T, double> predicate)

        {

            double toplam = 0;

 

            foreach (T obj in source)

            {

                toplam += predicate(obj);

            }

 

            return toplam / source.Say();

        }

    }

class Program

    {

        static void Main(string[] args)

        {

            Dukkan dukkan = new Dukkan();

 

            double sonuc3 = dukkan.Ortalama(u => u.Fiyat);

            Console.WriteLine("Kaynaktaki ürünlerin ortalam fiyatları : {0}", sonuc3);

 

        }

    }

image

All, Any

All metodu, kaynaktaki tüm elemanların koşulu sağlayıp sağlamadığına  bakar. Biri bile sağlamıyorsa false döner. Any metodu ise koşulu sağlayan en az bir nesne varsa true döner.

public static class BizimEnumerable

    {

        public static bool BizimAny<T>(this IEnumerable<T> source, Func<T, bool> predicate)

        {

            foreach (T obj in source)

            {

                if (predicate(obj))

                    return true;

            }

            return false;

        }

 

        public static bool BizimAll<T>(this IEnumerable<T> source, Func<T, bool> predicate)

        {

            foreach (T obj in source)

            {

                if (!predicate(obj))

                    return false;

            }

            return true;

        }

    }

class Program

    {

        static void Main(string[] args)

        {

            Dukkan dukkan = new Dukkan();

 

            bool sonuc = new int[] { 1, 2, 3, 4, 5, 6, 7 }.BizimAny(s => s % 2 == 1);

            Console.WriteLine(sonuc);

 

            Console.WriteLine("***********************************************");

 

            bool sonuc2 = new int[] { 1, 2, 3, 4, 5, 6 }.BizimAll(s => s < 10);

            Console.WriteLine(sonuc2);

 

        }

    }

image

ToArray

LINQ metotlarından geriye sorgulanabilir nesneler döndüğünden bahsetmiştik. İçerisinde iterasyon yapıldığında çalışan bu sorgu nesnelerini bir diziye almak için ToArray metodunu kullanırız. Şimdi de bu metodu kendimiz tasarlayalım.

public static T[] DiziyeDoldur<T>(this IEnumerable<T> source)

        {

            int index = 0;

            T[] dizi = new T[source.Say()];

 

            foreach (T obj in source)

            {

                dizi[index++] = obj;

                //index önce kullanılır, sonra değeri bir artırılır.

            }

 

            return dizi;

        }

Metodumuzu kullanarak dukkan içerisindeki ürünleri bir Urun dizisine dolduralım ve ilk ürünün adını ekrana yazdıralım.

class Program

    {

        static void Main(string[] args)

        {

            Dukkan dukkan = new Dukkan();

 

            Urun[] urunler = dukkan.DiziyeDoldur();

            Console.WriteLine(urunler[0].Ad);

        }

    }

image

First, FirstOrDefault()

First metodu, kaynakta eleman bulamassa exception fırlatır. FirstOrDefault ise o tipin default değerini getirir. Şimdi bu metotları tasarlayalım. Ayrıca birer tane aşırı yüklenmiş versiyonlarını ekleyerek, metot içerisinde filtreleme yapılabilmesini sağlayalım.

public static T IlkiniGetir<T>(this IEnumerable<T> source)

        {

            foreach (T obj in source)

            {

                return obj;

            }

 

            throw new NullReferenceException();

        }

 

        public static T IlkiniGetir<T>(this IEnumerable<T> source, Func<T, bool> predicate)

        {

            foreach (T obj in source)

            {

                if (predicate(obj))

                {

                    return obj;

                }

            }

            throw new NullReferenceException();

        }

 

        public static T IlkiniYadaVarsayilaniGetir<T>(this IEnumerable<T> source) where T : new()

        {

            T defaultType = new T();

 

            foreach (T obj in source)

            {

                return obj;

            }

 

            return defaultType;

        }

 

        public static T IlkiniYadaVarsayilaniGetir<T>(this IEnumerable<T> source, Func<T, bool> predicate) where T : new()

        {

            T defaultType = new T();

 

            foreach (T obj in source)

            {

                if (predicate(obj))

                {

                    return obj;

                }

            }

 

            return defaultType;

        }

NOT: Bizim yazdığımız “İlkiniYadaVarsayilaniGetir” metodu null yerine o tipten örneklenmiş ve default değerlere sahip bir nesne dönmektedir.

Buradaki “IlkiniYadaVarsayilaniGetir” metodu Dukkan tipi üzerinde çıkmayacaktır. Çünkü T tipine, parametre almayan bir constructor bulundurma zorunluluğu getirdik. Burada T tipi Urun sınıfıdır, Urun sınıfı içerisinde de tek bir constructor metot vardır, o da 3 parametre alan versiyonudur. Dolayısı ile bu metodu dukkan sınıfı üzerinde kullanabilmemiz için Urun sınıfına aşağıdaki constructor metodu ekleyelim.

public Urun()

        {

 

        }

Şimdi metotlarımızı test edebiliriz.

class Program

    {

        static void Main(string[] args)

        {

            Dukkan dukkan = new Dukkan();

 

            //Urun urn = dukkan.IlkiniGetir(u => u.Fiyat > 1000);

            //Fiyatı 1000'den büyük olan ürün olmadığı için exception alırız.

 

            Urun u1 = dukkan.Filtrele(u => u.Ad.StartsWith("M")).IlkiniGetir();

            Console.WriteLine(u1.Ad);

           

            Urun u2 = dukkan.IlkiniGetir(u => u.Fiyat == 1000);

            Console.WriteLine(u2.Ad);

 

            Urun u3 = dukkan.Filtrele(u => u.Fiyat < 100).IlkiniYadaVarsayilaniGetir();

            Console.WriteLine(u3.Ad);

           

            Urun u4 = dukkan.IlkiniYadaVarsayilaniGetir(u => u.Fiyat > 1000);

            if (u4.Ad == null)

                Console.WriteLine("Nesne elde edilemedi.");

        }

    }

image

Buraya kadar kendi LINQ metotlarımızı yazabilmemizin mümkün olduğunu gördük. İhtiyacımız olduğu durumlarda, amacımıza uygun bir LINQ metodu tasarlayabiliriz. Metot tasarımı yaparken yapılan olduğumuz kodlama sizlerin hayal dünyasına göre farklılık gösterebilir. Ben sadece küçük bir kısmını ele almaya çalıştım.

Faydalı olması dileği ile…



4 yorum:

Adsız dedi ki...

Harika örnekler.
Çok çok teşekkür ederim.

Adsız dedi ki...

Helal sana gerçekten çok güzel bir konuya değinmiş ve çok güzel açıklamışsın.

Unknown dedi ki...

Referans niteliğinde, Harika.

Onur Salkaya dedi ki...

Rica ederim. Beğenmenize sevindim

Yorum Gönder