LINQ'e genel bakış

Language-Integrated Sorgusu (LINQ), C# ve Visual Basic için dil düzeyinde sorgulama özellikleri ve daha yüksek sıralı bir işlev API'si sağlar. Bu api, açıklayıcı bildirim temelli kod yazmanızı sağlar.

Dil düzeyinde sorgu söz dizimi

Bu, dil düzeyinde sorgu söz dizimidir:

var linqExperts = from p in programmers
                  where p.IsNewToLINQ
                  select new LINQExpert(p);
Dim linqExperts = From p in programmers
                  Where p.IsNewToLINQ
                  Select New LINQExpert(p)

Bu, API'yi kullanan örnekle IEnumerable<T> aynıdır:

var linqExperts = programmers.Where(p => p.IsNewToLINQ)
                             .Select(p => new LINQExpert(p));
Dim linqExperts = programmers.Where(Function(p) p.IsNewToLINQ).
                             Select(Function(p) New LINQExpert(p))

LINQ ifade edicidir

Imagine bir evcil hayvan listeniz var, ancak bir evcil hayvana değerine göre doğrudan RFID erişebileceğiniz bir sözlüğe dönüştürmek istiyorsunuz.

Bu geleneksel kesinlik temelli koddur:

var petLookup = new Dictionary<int, Pet>();

foreach (var pet in pets)
{
    petLookup.Add(pet.RFID, pet);
}
Dim petLookup = New Dictionary(Of Integer, Pet)()

For Each pet in pets
    petLookup.Add(pet.RFID, pet)
Next

Kodun amacı yeni Dictionary<int, Pet> bir liste oluşturmak ve buna döngü yoluyla eklemek değildir; var olan bir listeyi sözlüğe dönüştürmektir! LINQ amacı korurken kesinlik temelli kod korunmaz.

Bu eşdeğer LINQ ifadesidir:

var petLookup = pets.ToDictionary(pet => pet.RFID);
Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)

LINQ kullanan kod, programcı olarak mantık yürütme sırasında amaç ve kod arasında yürütme alanını eşitlediğinden değerlidir. Bir diğer bonus da kod kısalığıdır. Imagine yukarıda olduğu gibi kod tabanının büyük bölümlerini 1/3 oranında azaltır. Tatlı anlaşma, değil mi?

LINQ sağlayıcıları veri erişimini basitleştirir

Vahşi ortamda önemli bir yazılım öbekleri için, her şey bir kaynaktan (Veritabanları, JSON, XML vb.) gelen verilerle ilgilenme etrafında döner. Bu genellikle her veri kaynağı için can sıkıcı olabilecek yeni bir API öğrenmeyi içerir. LINQ, veri erişiminin ortak öğelerini hangi veri kaynağını seçerseniz seçin aynı görünen bir sorgu söz dizimine soyutlayarak bunu basitleştirir.

Bu, belirli bir öznitelik değerine sahip tüm XML öğelerini bulur:

public static IEnumerable<XElement> FindAllElementsWithAttribute(XElement documentRoot, string elementName,
                                           string attributeName, string value)
{
    return from el in documentRoot.Elements(elementName)
           where (string)el.Element(attributeName) == value
           select el;
}
Public Shared Function FindAllElementsWithAttribute(documentRoot As XElement, elementName As String,
                                           attributeName As String, value As String) As IEnumerable(Of XElement)
    Return From el In documentRoot.Elements(elementName)
           Where el.Element(attributeName).ToString() = value
           Select el
End Function

Bu görevi gerçekleştirmek için XML belgesinde el ile geçiş yapmak için kod yazmak çok daha zor olabilir.

LINQ Sağlayıcılarıyla yapabileceğiniz tek şey XML ile etkileşim kurmanız değildir. Linq to SQL, MSSQL Server Veritabanı için oldukça çıplak bir Object-Relational Eşleyicidir (ORM). Json.NET kitaplığı LINQ aracılığıyla verimli JSON Belgesi geçişi sağlar. Ayrıca, ihtiyacınız olanı yerine getiren bir kitaplık yoksa kendi LINQ Sağlayıcınızı da yazabilirsiniz!

Sorgu söz dizimini kullanma nedenleri

Sorgu söz dizimi neden kullanılır? Bu sık sık sorulan bir sorudur. Sonuçta, aşağıdaki kod:

var filteredItems = myItems.Where(item => item.Foo);
Dim filteredItems = myItems.Where(Function(item) item.Foo)

aşağıdakilerden çok daha kısadır:

var filteredItems = from item in myItems
                    where item.Foo
                    select item;
Dim filteredItems = From item In myItems
                    Where item.Foo
                    Select item

API söz dizimi, sorgu söz dizimini gerçekleştirmenin daha kısa bir yolu değil mi?

Hayır. Sorgu söz dizimi, ifadenin sonraki parçalarında kullanarak ifade kapsamında bir değişken tanıtıp bağlamanıza olanak tanıyan let yan tümcesinin kullanılmasına olanak tanır. Aynı kodun yalnızca API söz dizimi ile yeniden üretilmesi yapılabilir, ancak büyük olasılıkla okunmasının zor olduğu bir koda yol açar.

Bu durumda soru sorulmaya neden oluyor, sorgu söz dizimini kullanmanız mı gerekiyor?

Şu durumda bu sorunun yanıtı evet olur:

  • Mevcut kod tabanınız sorgu söz dizimini zaten kullanıyor.
  • Karmaşıklık nedeniyle sorgularınızdaki değişkenleri kapsamanız gerekir.
  • Sorgu söz dizimini tercih ettiğiniz için kod tabanınızın dikkatini dağıtmaz.

Bu sorunun yanıtı hayır ise...

  • Mevcut kod tabanınız zaten API söz dizimini kullanıyor
  • Sorgularınızdaki değişkenlerin kapsamını değiştirmeniz gerekmez
  • API söz dizimini tercih ettiğiniz için kod tabanınızın dikkatini dağıtmaz

Temel LINQ

LINQ örneklerinin gerçekten kapsamlı bir listesi için 101 LINQ Örnekleri'ni ziyaret edin.

Aşağıdaki örnekler, LINQ'in bazı temel parçalarının hızlı bir gösterimidir. LINQ burada gösterilenden daha fazla işlevsellik sağladığından bu hiçbir şekilde kapsamlı değildir.

Ekmek ve tereyağı - Where, Selectve Aggregate

// Filtering a list.
var germanShepherds = dogs.Where(dog => dog.Breed == DogBreed.GermanShepherd);

// Using the query syntax.
var queryGermanShepherds = from dog in dogs
                          where dog.Breed == DogBreed.GermanShepherd
                          select dog;

// Mapping a list from type A to type B.
var cats = dogs.Select(dog => dog.TurnIntoACat());

// Using the query syntax.
var queryCats = from dog in dogs
                select dog.TurnIntoACat();

// Summing the lengths of a set of strings.
int seed = 0;
int sumOfStrings = strings.Aggregate(seed, (s1, s2) => s1.Length + s2.Length);
' Filtering a list.
Dim germanShepherds = dogs.Where(Function(dog) dog.Breed = DogBreed.GermanShepherd)

' Using the query syntax.
Dim queryGermanShepherds = From dog In dogs
                          Where dog.Breed = DogBreed.GermanShepherd
                          Select dog

' Mapping a list from type A to type B.
Dim cats = dogs.Select(Function(dog) dog.TurnIntoACat())

' Using the query syntax.
Dim queryCats = From dog In dogs
                Select dog.TurnIntoACat()

' Summing the lengths of a set of strings.
Dim seed As Integer = 0
Dim sumOfStrings As Integer = strings.Aggregate(seed, Function(s1, s2) s1.Length + s2.Length)

Liste listesini düzleştirme

// Transforms the list of kennels into a list of all their dogs.
var allDogsFromKennels = kennels.SelectMany(kennel => kennel.Dogs);
' Transforms the list of kennels into a list of all their dogs.
Dim allDogsFromKennels = kennels.SelectMany(Function(kennel) kennel.Dogs)

İki küme arasındaki birleşim (özel karşılaştırıcı ile)

public class DogHairLengthComparer : IEqualityComparer<Dog>
{
    public bool Equals(Dog a, Dog b)
    {
        if (a == null && b == null)
        {
            return true;
        }
        else if ((a == null && b != null) ||
                 (a != null && b == null))
        {
            return false;
        }
        else
        {
            return a.HairLengthType == b.HairLengthType;
        }
    }

    public int GetHashCode(Dog d)
    {
        // Default hashcode is enough here, as these are simple objects.
        return d.GetHashCode();
    }
}
...

// Gets all the short-haired dogs between two different kennels.
var allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, new DogHairLengthComparer());

Public Class DogHairLengthComparer
  Inherits IEqualityComparer(Of Dog)

  Public Function Equals(a As Dog,b As Dog) As Boolean
      If a Is Nothing AndAlso b Is Nothing Then
          Return True
      ElseIf (a Is Nothing AndAlso b IsNot Nothing) OrElse (a IsNot Nothing AndAlso b Is Nothing) Then
          Return False
      Else
          Return a.HairLengthType = b.HairLengthType
      End If
  End Function

  Public Function GetHashCode(d As Dog) As Integer
      ' Default hashcode is enough here, as these are simple objects.
      Return d.GetHashCode()
  End Function
End Class

...

' Gets all the short-haired dogs between two different kennels.
Dim allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, New DogHairLengthComparer())

İki küme arasındaki kesişim

// Gets the volunteers who spend share time with two humane societies.
var volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
                                                     new VolunteerTimeComparer());
' Gets the volunteers who spend share time with two humane societies.
Dim volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
                                                     New VolunteerTimeComparer())

Sıralama

// Get driving directions, ordering by if it's toll-free before estimated driving time.
var results = DirectionsProcessor.GetDirections(start, end)
              .OrderBy(direction => direction.HasNoTolls)
              .ThenBy(direction => direction.EstimatedTime);
' Get driving directions, ordering by if it's toll-free before estimated driving time.
Dim results = DirectionsProcessor.GetDirections(start, end).
                OrderBy(Function(direction) direction.HasNoTolls).
                ThenBy(Function(direction) direction.EstimatedTime)

Örnek özelliklerinin eşitliği

Son olarak, daha gelişmiş bir örnek: aynı türdeki iki örneğin özelliklerinin değerlerinin eşit olup olmadığını belirleme ( Bu StackOverflow gönderisinden ödünç alınıp değiştirildi):

public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
    if (self == null || to == null)
    {
        return self == to;
    }

    // Selects the properties which have unequal values into a sequence of those properties.
    var unequalProperties = from property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                            where !ignore.Contains(property.Name)
                            let selfValue = property.GetValue(self, null)
                            let toValue = property.GetValue(to, null)
                            where !Equals(selfValue, toValue)
                            select property;
    return !unequalProperties.Any();
}
<System.Runtime.CompilerServices.Extension()>
Public Function PublicInstancePropertiesEqual(Of T As Class)(self As T, [to] As T, ParamArray ignore As String()) As Boolean
    If self Is Nothing OrElse [to] Is Nothing Then
        Return self Is [to]
    End If

    ' Selects the properties which have unequal values into a sequence of those properties.
    Dim unequalProperties = From [property] In GetType(T).GetProperties(BindingFlags.Public Or BindingFlags.Instance)
                            Where Not ignore.Contains([property].Name)
                            Let selfValue = [property].GetValue(self, Nothing)
                            Let toValue = [property].GetValue([to], Nothing)
                            Where Not Equals(selfValue, toValue) Select [property]
    Return Not unequalProperties.Any()
End Function

PLINQ

PLINQ veya Paralel LINQ, LINQ ifadeleri için paralel bir yürütme altyapısıdır. Başka bir deyişle, normal bir LINQ ifadesi herhangi bir sayıda iş parçacığı arasında önemsiz bir şekilde paralelleştirilebilir. Bu, ifadeden önce çağrısıyla AsParallel() gerçekleştirilir.

Aşağıdaki topluluklara bir göz atın:

public static string GetAllFacebookUserLikesMessage(IEnumerable<FacebookUser> facebookUsers)
{
    var seed = default(UInt64);

    Func<UInt64, UInt64, UInt64> threadAccumulator = (t1, t2) => t1 + t2;
    Func<UInt64, UInt64, UInt64> threadResultAccumulator = (t1, t2) => t1 + t2;
    Func<Uint64, string> resultSelector = total => $"Facebook has {total} likes!";

    return facebookUsers.AsParallel()
                        .Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector);
}
Public Shared GetAllFacebookUserLikesMessage(facebookUsers As IEnumerable(Of FacebookUser)) As String
{
    Dim seed As UInt64 = 0

    Dim threadAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2
    Dim threadResultAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2
    Dim resultSelector As Func(Of Uint64, string) = Function(total) $"Facebook has {total} likes!"

    Return facebookUsers.AsParallel().
                        Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector)
}

Bu kod, sistem iş parçacıkları arasında gerektiği şekilde bölümlenecek facebookUsers , her iş parçacığındaki toplam beğenileri paralel olarak toplayacak, her iş parçacığı tarafından hesaplanan sonuçları ve güzel bir dizeyle sonuçlanan projeyi toplayacak.

Diyagram biçiminde:

PLINQ diagram

LINQ aracılığıyla kolayca ifade edilebilen paralelleştirilebilir CPU'ya bağlı işler (başka bir deyişle saf işlevlerdir ve yan etkisi yoktur) PLINQ için harika bir adaydır. Yan etkisi olan işler için Görev Paralel Kitaplığı'nı kullanmayı göz önünde bulundurun.

Diğer kaynaklar

  • 101 LINQ Örnekleri
  • Linqpad, bir oyun alanı ortamı ve C#/F#/Visual Basic için Veritabanı sorgulama altyapısı
  • LINQ-to-objects'in nasıl uygulandığını öğrenmek için bir e-kitap olan EduLinq