Dela via


LINQ-översikt

Language-Integrated Query (LINQ) tillhandahåller frågefunktioner på språknivå och ett funktions-API med högre ordning till C# och Visual Basic som gör att du kan skriva uttrycksfull deklarativ kod.

Frågesyntax på språknivå

Det här är frågesyntaxen på språknivå:

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)

Det här är samma exempel med hjälp av API:et IEnumerable<T> :

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 är uttrycksfullt

Imagine du har en lista över husdjur, men vill konvertera den till en ordlista där du kan komma åt ett husdjur direkt med dess RFID värde.

Detta är traditionell imperativ kod:

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

Avsikten med koden är inte att skapa en ny Dictionary<int, Pet> och lägga till den via en loop, utan att konvertera en befintlig lista till en ordlista! LINQ bevarar avsikten medan den imperativa koden inte gör det.

Det här är motsvarande LINQ-uttryck:

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

Koden som använder LINQ är värdefull eftersom den jämnar ut spelplanen mellan avsikt och kod vid resonemang som programmerare. En annan bonus är kort kod. Imagine att minska stora delar av en kodbas med 1/3 enligt ovan. Söt affär, eller hur?

LINQ-leverantörer förenklar dataåtkomst

För en stor del av programvaran ute i naturen handlar allt om att hantera data från vissa källor (databaser, JSON, XML och så vidare). Ofta handlar det om att lära sig ett nytt API för varje datakälla, vilket kan vara irriterande. LINQ förenklar detta genom att abstrahera vanliga element för dataåtkomst till en frågesyntax som ser likadan ut oavsett vilken datakälla du väljer.

Detta hittar alla XML-element med ett specifikt attributvärde:

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

Det skulle vara mycket svårare att skriva kod för att manuellt bläddra i XML-dokumentet för att utföra den här uppgiften.

Att interagera med XML är inte det enda du kan göra med LINQ-providers. Linq to SQL är en ganska bare-bones Object-Relational Mapper (ORM) för en MSSQL Server Database. Json.NET-biblioteket ger effektiv JSON-dokumentbläddering via LINQ. Om det inte finns ett bibliotek som gör det du behöver kan du också skriva en egen LINQ-provider!

Orsaker till att använda frågesyntaxen

Varför använda frågesyntax? Det här är en fråga som ofta dyker upp. När allt kommer runt, följande kod:

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

är mycket mer koncist än så här:

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

Är inte API-syntaxen bara ett mer koncist sätt att utföra frågesyntaxen?

Nej. Frågesyntaxen tillåter användning av let-satsen , vilket gör att du kan introducera och binda en variabel inom uttryckets omfång, med hjälp av den i efterföljande delar av uttrycket. Det går att återskapa samma kod med bara API-syntaxen, men det leder troligen till kod som är svår att läsa.

Så detta väcker frågan, ska du bara använda frågesyntaxen?

Svaret på den här frågan är ja om:

  • Din befintliga kodbas använder redan frågesyntaxen.
  • Du måste omfångsvariabler i dina frågor på grund av komplexiteten.
  • Du föredrar frågesyntaxen och den distraherar inte från din kodbas.

Svaret på den här frågan är nej om...

  • Din befintliga kodbas använder redan API-syntaxen
  • Du behöver inte omfångsvariabler i dina frågor
  • Du föredrar API-syntaxen och den distraherar inte från din kodbas

Grundläggande LINQ

En fullständig lista över LINQ-exempel finns i 101 LINQ-exempel.

Följande exempel är en snabb demonstration av några av de viktigaste delarna av LINQ. Det här är inte på något sätt heltäckande eftersom LINQ tillhandahåller fler funktioner än vad som visas här.

Brödet och smöret - Where, Selectoch 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)

Platta ut en lista med listor

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

Union mellan två uppsättningar (med anpassad jämförelse)

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

Skärningspunkt mellan två uppsättningar

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

Ordna profiler

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

Likhet mellan instansegenskaper

Slutligen ett mer avancerat exempel: avgöra om värdena för egenskaperna för två instanser av samma typ är lika med (Lånat och ändrat från det här StackOverflow-inlägget):

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, eller Parallel LINQ, är en parallell körningsmotor för LINQ-uttryck. Med andra ord kan ett vanligt LINQ-uttryck parallelliseras trivialt över valfritt antal trådar. Detta görs via ett anrop till AsParallel() före uttrycket.

Tänk också på följande:

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

Den här koden partitioneras facebookUsers mellan systemtrådar efter behov, summerar det totala antalet likes på varje tråd parallellt, summerar resultaten som beräknas av varje tråd och projicerar resultatet till en fin sträng.

I diagramform:

PLINQ diagram

Parallelliserbara CPU-bundna jobb som enkelt kan uttryckas via LINQ (med andra ord är rena funktioner och har inga biverkningar) är en bra kandidat för PLINQ. För jobb som har en sidoeffekt bör du överväga att använda det parallella aktivitetsbiblioteket.

Fler resurser

  • 101 LINQ-exempel
  • Linqpad, en lekplatsmiljö och databasfrågemotor för C#/F#/Visual Basic
  • EduLinq, en e-bok för att lära sig hur LINQ-to-objects implementeras