Skip to content

İki farklı Generic List’i Linq ile eşleştirmek

Elimizde değişik bir case var, şöyle ki; bir list(of string) içerisinde n adet string değeri, diğer bir list(of integer) içerisinde ise yine n adet puan var. Biz istiyoruz ki bu birinci list’ten (adı cevaplar olsun) =”D” olan item’lara karşılık gelen sırada ikinci list’teki (adı puanlar olsun) integer’lar toplansın. Şematize de edelim;
Cevaplar Listesi; (List (Of String))
D
Y
D
D
Y

Puanlar Listesi; (List (Of Integer))
4
5
6
3
4

Bu durumda D’lere karşılık gelen puanların toplamı lazım bize; Sonuç: (4 + 6 + 3) = 13 verecek bir fonksiyon ihtiyacımız var. Şimdi her kişinin aklına geldiği üzere bir toplam diye değişken integer yaratıp for each’ler le dönen değerleri toplarız, tabi ki olur. Fakat buradaki case bunu linq ile ve tek satırda yapmak.

Öncelikle ne lazım, bu listlerde değerin hangi sırada olduğunu öğrenebileceğimiz bir sistem. Generic List içerisinde bir fonksiyon var IndexOf adında fakat bu fonksiyon yukarıdan itibaren ilk eşleşen sonucu vereceğinden sağlıklı bir sistem olamaz. Yani cevaplar.IndexOf(“D”) her zaman 1 dönecektir. Sonuçda 4 + 4 + 4 olacak, kısaca hatalı olacaktır. O zaman bizim bu List’teki elemanlara erişebilmemiz için bir “unique identifier” ‘a ihtiyacımız var ki, veritabanlarında yaptığımız gibi direk olarak nokta atış list’e ait değere erişebilelim. Burada aklımıza gelen çözüm Generic List’i Generic Dictionary’ye çevirmek oluyor haliyle. Fakat daha öncede bahsettiğim ToDictionary Extension Method’u otomatik olarak bu unique identifier’i yaratmıyor. O halde ilk olarak bu ToDictionary methodunu bu duruma uygun hale getirelim. Buyrun;

Module Extensions
    <System.Runtime.CompilerServices.Extension()> _
    Function ToDictionary(Of TKey, TValue)(ByVal lst As List(Of TValue)) As Dictionary(Of TKey, TValue)
        Dim generic As Type = GetType(Dictionary(Of ,))
        Dim dict

        If GetType(TKey) Is GetType(Guid) Then
            Dim typeArgs() As Type = {GetType(Guid), lst.GetType.GetGenericArguments(0)}
            Dim constructed As Type = generic.MakeGenericType(typeArgs)
            dict = Activator.CreateInstance(constructed)

            For Each itm In lst
                dict.Add(Guid.NewGuid, itm)
            Next
        ElseIf GetType(TKey) Is GetType(Integer) Then
            Dim dct As New Dictionary(Of Object, TValue)
            Dim typeArgs() As Type = {GetType(Integer), lst.GetType.GetGenericArguments(0)}
            Dim constructed As Type = generic.MakeGenericType(typeArgs)
            dict = Activator.CreateInstance(constructed)

            Dim i As Integer = 0
            For Each itm In lst
                dict.Add(i, itm)
                i += 1
            Next
        Else
            Throw New NotSupportedException("Sadece GUID ve Integer anahtarlar destekleniyor")
        End If

        Return dict
    End Function

    <System.Runtime.CompilerServices.Extension()> _
    Function ToDictionary(Of TValue)(ByVal lst As List(Of TValue)) As Dictionary(Of Integer, TValue)
        Return ToDictionary(Of Integer, TValue)(lst)
    End Function
End Module

Kısaca gözatarsak, Generic List’e iki adet extension method ekliyoruz: birinci ui olarak hem GUID hem Integer kabul eden bir metod, diğeri ise otomatik olarak integer ui ekleyen bir metod. İkinci method integer parametresi ile ilk methodu çağırıyor zaten. Birinci metod’da önce boş bir Generic Dictionary için type nesnesi’ni alıyor. Sonra bu type dan duruma göre GUID ya da Integer tipte key parametreli olarak generic dictionary type’ı elde ediyoruz. Sonra bunu daha önceki bir yazıda anlattığım genel tanımlı fabrikamız Activator.CreateInstance ile yaratıyor ve içini dolduruyoruz.

Aslında bu yukarıdaki case için GUID gereksiz bir durumda fakat, yazarken Reflection mantığının ne kadar esneyebileceğini göstermek için o kısımlarıda yazdım. Bundan başka aynı durumdan ui olarak mesela genere edilmiş benzersiz String’ler vs. de kullanabiliriz. Sadece iki satırı değiştirmek yeterli oluyor. Neyse bu yukarıdaki durumda bize sıra durumlarının dönmesi gerekeceği için sadece integer kullanacağız.

Şimdi ise tek satırda olayı bitiren linq sorgumuzu yazalım;

Aggregate puan In (From cevap In cevaplar.ToDictionary(Of Integer)() Where cevap.Value = "D" Select puanlar(cevap.Key)) Into Sum(puan)

Tek satırda hem eşleştirmeyi hem de toplama işlemini yaptık. 10 satırı 1 satır yazalım derken fazladan 37 satır yazdık sanırım fakat bu tip Generic List’ten ui sahibi Generic Dictionary yaratan bir extension method’u kullanabileceğimiz yerler düşünebileceğinizden fazla.

Oldu madem, girdi olarak Generic List değil, Array alan ToDictionary extension method’unu da yazalım;

<System.Runtime.CompilerServices.Extension()> _
       Function ToDictionary(Of TKey, TValue)(ByVal arr As Array) As Dictionary(Of TKey, TValue)
        Dim generic As Type = GetType(Dictionary(Of ,))
        Dim dict

        If GetType(TKey) Is GetType(Guid) Then
            Dim typeArgs() As Type = {GetType(Guid), arr.GetType.GetElementType}
            Dim constructed As Type = generic.MakeGenericType(typeArgs)
            dict = Activator.CreateInstance(constructed)

            For Each itm In arr
                dict.Add(Guid.NewGuid, itm)
            Next
        ElseIf GetType(TKey) Is GetType(Integer) Then
            Dim dct As New Dictionary(Of Object, TValue)
            Dim typeArgs() As Type = {GetType(Integer), arr.GetType.GetElementType}
            Dim constructed As Type = generic.MakeGenericType(typeArgs)
            dict = Activator.CreateInstance(constructed)

            Dim i As Integer = 0
            For Each itm In arr
                dict.Add(i, itm)
                i += 1
            Next
        Else
            Throw New NotSupportedException("Sadece GUID ve Integer anahtarlar destekleniyor")
        End If

        Return dict
    End Function

    <System.Runtime.CompilerServices.Extension()> _
   Function ToDictionary(Of TValue)(ByVal arr As Array) As Dictionary(Of Integer, TValue)
        Return ToDictionary(Of Integer, TValue)(arr)
    End Function

Burada biraz karışık bir durum mevcut, dikkat ederseniz çıktı bir generic dictionary yaratırken array’in içerisindeki element type’ı alıyoruz. Halbuki yukarıdan gelen bir TValue type’ı da mevcut. TValue mutlaka almak zorundayız ki çıktı olarak bir Dictionary üretelim, yani tanımlama anında arr değişkeninin tipinden bihaber olacağımız için çıktıyı Object vermemek için böyle bir zorunluluğumuz var. E o zaman generic dictionary yaratımı esnasında TValue’yu kullanalım, mümkün fakat kullanıcının verdiği TValue ile Array’in içerisindeki Element Type tutmuyorsa ne olacak? Basit, bu şekliyle kalınca Return anında, kalmazda TValue dersek dict.add anında tip çevrim hatası verecek. Belki bununda kodun başında bir test ettirip hatayı en baştan fırlatmak lazım. Ama bu saatte onunla uğraşamayacağım açıkcası…

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*