Sdílet prostřednictvím


Přehled LINQ

Language-Integrated Query (LINQ) poskytuje možnosti dotazování na úrovni jazyka a funkce vyššího řádu rozhraní API pro C# a Visual Basic, které umožňují psát výrazný deklarativní kód.

Syntaxe dotazů na úrovni jazyka

Toto je syntaxe dotazů na úrovni jazyka:

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)

Toto je stejný příklad použití rozhraní 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 je výstižná

Představte si, že máte seznam domácích mazlíčků, ale chcete ho převést na slovník, kde můžete přistupovat k domácímu mazlíčku přímo podle jeho RFID hodnoty.

Toto je tradiční imperativní kód:

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

Záměrem kódu není vytvořit nový Dictionary<int, Pet> a přidávat do něj pomocí smyčky, ale převést stávající seznam na slovník. LINQ zachovává záměr, zatímco imperativní kód ne.

Toto je ekvivalentní výraz LINQ:

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

Kód využívající LINQ je cenný, protože při programátorském uvažování vyrovnává podmínky mezi záměrem a kódem. Dalším bonusem je stručnost kódu. Představte si, že snížíte velké části základu kódu o 1/3 podle výše uvedeného postupu. Sladká dohoda, že?

Zprostředkovatelé LINQ zjednodušují přístup k datům

U značné části softwaru dostupného v běžném prostředí se všechno točí kolem zpracování dat z nějakého zdroje (databáze, JSON, XML atd.). Často se to týká učení nového rozhraní API pro každý zdroj dat, což může být nepříjemné. LINQ to zjednodušuje abstrakcí běžných prvků přístupu k datům do syntaxe dotazu, která vypadá stejně bez ohledu na to, který zdroj dat vyberete.

Vyhledá všechny elementy XML s konkrétní hodnotou atributu:

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

Psaní kódu pro ruční procházení dokumentu XML k tomuto úkolu by bylo mnohem náročnější.

Interakce s XML není jediná věc, kterou můžete dělat s zprostředkovateli LINQ. Linq to SQL je poměrně holý mapovač Object-Relational mapper (ORM) pro databázi serveru MSSQL. Knihovna Json.NET poskytuje efektivní procházení dokumentů JSON prostřednictvím LINQ. Kromě toho, pokud neexistuje knihovna, která dělá to, co potřebujete, můžete také napsat vlastního poskytovatele LINQ!

Důvody použití syntaxe dotazu

Proč používat syntaxi dotazu? To je otázka, která se často objevuje. Koneckonců, následující kód:

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

je mnohem stručnější než toto:

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

Není syntaxe rozhraní API jen stručnější způsob, jak syntaxi dotazu provést?

Ne. Syntaxe dotazu umožňuje použití klauzule let, která umožňuje zavést a svázat proměnnou v rámci výrazu a použít ji v dalších částech výrazu. Reprodukování stejného kódu pouze se syntaxí rozhraní API je možné provést, ale s největší pravděpodobností povede ke čtení kódu, který je obtížně čitelný.

Takže to vyvolává otázku, měli byste použít jenom syntaxi dotazu?

Odpověď na tuto otázku je ano, pokud:

  • Váš existující základ kódu už používá syntaxi dotazu.
  • Proměnné v rámci dotazů je potřeba vymezit kvůli složitosti.
  • Preferujete syntaxi dotazů a nebude vás rozptylovat od vaší kódové základny.

Odpověď na tuto otázku je bez pokud...

  • Váš stávající základ kódu už používá syntaxi rozhraní API.
  • V dotazech není potřeba definovat rozsah proměnných.
  • Dáváte přednost syntaxi rozhraní API a neodvádí pozornost od vašeho kódu.

Základní LINQ

Následující příklady představují rychlou ukázku některých základních částí LINQ. To není nijak komplexní, protože LINQ poskytuje více funkcí, než je zde prezentováno.

Chléb a máslo - Where, Selecta 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, (partialSum, nextString) => partialSum + nextString.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(partialSum, nextString) partialSum + nextString.Length)

Zploštění seznamu seznamů

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

Sjednocení dvou množin (s vlastním komparátorem)

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

Průnik dvou sad

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

Objednávání

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

Rovnost vlastností instance

A konečně pokročilejší příklad: určení, zda hodnoty vlastností dvou instancí stejného typu jsou stejné (převzato a upraveno z tohoto StackOverflow příspěvku):

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 nebo Parallel LINQ je paralelní prováděcí modul pro výrazy LINQ. Jinými slovy, regulární výraz LINQ lze triviálně paralelizovat napříč libovolným počtem vláken. Toho se dosahuje voláním AsParallel() před výrazem.

Vezměte v úvahu následující skutečnosti:

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

Tento kód podle potřeby rozdělí facebookUsers napříč systémovými vlákny, paralelně sečte celkový počet lajků na každém vlákně, sečte výsledky vypočítané jednotlivými vlákny a vyjádří tento výsledek do přehledného řetězce.

V podobě diagramu:

diagram PLINQ

Paralelizovatelné úlohy vázané na procesor, které lze snadno vyjádřit prostřednictvím LINQ (jinými slovy, jsou čisté funkce a nemají žádné vedlejší účinky) jsou skvělým kandidátem pro PLINQ. U úloh, které mají vedlejší účinek, zvažte použitíparalelní knihovny úloh .

Další zdroje informací

  • Linqpad, prostředí dětského hřiště a databázový dotazovací modul pro C#/F#/Visual Basic
  • EduLinq, elektronická kniha pro seznámení s implementací LINQ-to-objects