Delen via


LINQ-overzicht

Language-Integrated Query (LINQ) biedt querymogelijkheden op taalniveau en een functie-API met een hogere volgorde naar C# en Visual Basic, waarmee u expressieve declaratieve code kunt schrijven.

Querysyntaxis op taalniveau

Dit is de querysyntaxis op taalniveau:

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)

Dit is hetzelfde voorbeeld met behulp van de IEnumerable<T> API:

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 is expressief

Imagine u een lijst met huisdieren hebt, maar deze wilt omzetten in een woordenlijst waar u rechtstreeks toegang hebt tot een huisdier op basis van de RFID waarde.

Dit is traditionele imperatieve code:

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

De bedoeling achter de code is niet om een nieuwe Dictionary<int, Pet> code te maken en er via een lus aan toe te voegen. Het is om een bestaande lijst te converteren naar een woordenlijst. LINQ behoudt de intentie, terwijl de imperatieve code dat niet doet.

Dit is de equivalente LINQ-expressie:

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

De code die LINQ gebruikt, is waardevol omdat het spelveld tussen intentie en code zelfs wordt gehalveert wanneer u redeneert als programmeur. Een andere bonus is code-breviteit. Imagine het verminderen van grote delen van een codebasis met 1/3 zoals hierboven is gedaan. Mooie deal, toch?

LINQ-providers vereenvoudigen de toegang tot gegevens

Voor een aanzienlijk deel van de software in het wild draait alles om het omgaan met gegevens uit een bepaalde bron (Databases, JSON, XML, enzovoort). Dit omvat vaak het leren van een nieuwe API voor elke gegevensbron, wat vervelend kan zijn. LINQ vereenvoudigt dit door algemene elementen van gegevenstoegang te abstraheren in een querysyntaxis die er hetzelfde uitziet, ongeacht welke gegevensbron u kiest.

Hiermee vindt u alle XML-elementen met een specifieke kenmerkwaarde:

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

Het schrijven van code om het XML-document handmatig te doorlopen om deze taak uit te voeren, zou veel moeilijker zijn.

Interactie met XML is niet het enige wat u kunt doen met LINQ-providers. Linq naar SQL is een vrij lege botten Object-Relational Mapper (ORM) voor een MSSQL Server-database. De Json.NET-bibliotheek biedt efficiënte JSON-documentkruising via LINQ. Bovendien, als er geen bibliotheek is die doet wat u nodig hebt, kunt u ook uw eigen LINQ Provider schrijven!

Redenen om de querysyntaxis te gebruiken

Waarom querysyntaxis gebruiken? Dit is een vraag die vaak opkomt. Immers, de volgende code:

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

is veel beknopter dan dit:

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

Is de API-syntaxis niet slechts een beknoptere manier om de querysyntaxis uit te voeren?

Nee. Met de querysyntaxis kunt u de let-component gebruiken, waarmee u een variabele binnen het bereik van de expressie kunt introduceren en binden, waarbij u deze kunt gebruiken in volgende delen van de expressie. Het reproduceren van dezelfde code met alleen de API-syntaxis kan worden uitgevoerd, maar leidt waarschijnlijk tot code die moeilijk te lezen is.

Dus dit smekt de vraag, moet u gewoon de querysyntaxis gebruiken?

Het antwoord op deze vraag is ja als:

  • Uw bestaande codebasis maakt al gebruik van de querysyntaxis.
  • U moet variabelen binnen uw query's bereiken vanwege complexiteit.
  • U geeft de voorkeur aan de querysyntaxis en leidt niet af van uw codebasis.

Het antwoord op deze vraag is nee als...

  • Uw bestaande codebasis maakt al gebruik van de API-syntaxis
  • U hoeft geen bereikvariabelen binnen uw query's te bereiken
  • U geeft de voorkeur aan de API-syntaxis en leidt niet af van uw codebasis

Essentiële LINQ

Ga naar 101 LINQ Samples voor een echt uitgebreide lijst met LINQ-voorbeelden.

De volgende voorbeelden zijn een korte demonstratie van enkele van de essentiële onderdelen van LINQ. Dit is op geen enkele manier uitgebreid, omdat LINQ meer functionaliteit biedt dan wat hier wordt weergegeven.

Het brood en boter - Where, Selecten 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)

Een lijst met lijsten platmaken

// 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)

Samenvoeging tussen twee sets (met aangepaste comparator)

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())

Snijpunt tussen twee sets

// 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())

Ordenen

// 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)

Gelijkheid van exemplaareigenschappen

Ten slotte een geavanceerdere steekproef: bepalen of de waarden van de eigenschappen van twee exemplaren van hetzelfde type gelijk zijn (Geleend en gewijzigd op basis van deze StackOverflow-post):

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, of Parallel LINQ, is een parallelle uitvoeringsengine voor LINQ-expressies. Met andere woorden, een reguliere LINQ-expressie kan trivially parallel worden geparallelliseerd over een willekeurig aantal threads. Dit wordt bereikt via een aanroep die voorafgaat aan AsParallel() de expressie.

Overweeg de volgende:

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)
}

Met deze code wordt zo nodig over systeemthreads gepartitioneerd facebookUsers , het totaal aantal likes voor elke thread parallel opgeteld, de resultaten opgeteld door elke thread en het project dat resulteert in een mooie tekenreeks.

In diagramvorm:

PLINQ diagram

Parallelliseerbare CPU-gebonden taken die eenvoudig kunnen worden uitgedrukt via LINQ (met andere woorden, zijn pure functies en hebben geen neveneffecten) zijn een uitstekende kandidaat voor PLINQ. Voor taken die een neveneffect hebben, kunt u overwegen de taakparallelbibliotheek te gebruiken.

Meer bronnen

  • 101 LINQ-voorbeelden
  • Linqpad, een speeltuinomgeving en databasequery-engine voor C#/F#/Visual Basic
  • EduLinq, een e-book om te leren hoe LINQ-to-objects worden geïmplementeerd