Přehled LINQ

Language-Integrated Query (LINQ) poskytuje možnosti dotazování na úrovni jazyka a rozhraní API funkcí s vyšším pořadím pro C# a Visual Basic, které umožňují psát expresní 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í IEnumerable<T> rozhraní 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ýrazná

Imagine máte seznam domácích mazlíčků, ale chcete jej 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řidat ho přes smyčku, je převést existující seznam do slovníku. 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 i hrací pole mezi záměrem a kódem při odůvodnění jako programátor. Dalším bonusem je stručnost kódu. Imagine snížení velkých částí základu kódu o 1/3, jak je uvedeno výše. Sladká dohoda, že?

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

U významného kusu softwaru ve volné přírodě se všechno točí kolem práce s daty 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 tím, že abstrahuje běžné prvky přístupu k datům do syntaxe dotazu, která vypadá stejně bez ohledu na to, jaký 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 poskytovateli LINQ. Linq to SQL je poměrně holá Object-Relational mapovač (ORM) pro databázi serveru MSSQL. Knihovna Json.NET poskytuje efektivní procházení dokumentů JSON prostřednictvím LINQ. Pokud navíc není knihovna, která dělá to, co potřebujete, můžete také napsat vlastního zprostředkovatele 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?

No. Syntaxe dotazu umožňuje použití klauzule let , která umožňuje zavést a svázat proměnnou v rámci rozsahu výrazu a použít ji v následný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 k obtížně čitelnému kódu.

Takže se jedná o otázku, měli byste použít jenom syntaxi dotazu?

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

  • Váš stávající základ kódu už používá syntaxi dotazu.
  • Proměnné v rámci dotazů potřebujete oborovat kvůli složitosti.
  • Dáváte přednost syntaxi dotazu a nebude vás rušit od základu kódu.

Odpověď na tuto otázku není, pokud...

  • Váš stávající základ kódu už používá syntaxi rozhraní API.
  • Proměnné v rámci dotazů nemusíte oborovat.
  • Dáváte přednost syntaxi rozhraní API a nebudete rušit od základu kódu.

Základní LINQ

Úplný seznam ukázek LINQ najdete v 101 ukázkách 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, (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)

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í mezi dvěma sadami (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 mezi dvěma sadami

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

Řazení

// 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ší ukázka: určení, jestli jsou hodnoty vlastností dvou instancí stejného typu stejné (Vypůjčené a změněné z tohoto příspěvku StackOverflow):

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 modul paralelního spouštění pro výrazy LINQ. Jinými slovy, regulární výraz LINQ lze triviálně paralelizovat napříč libovolným počtem vláken. To se provádí voláním AsParallel() předcházejícího výrazu.

Zvažte použití těchto zdrojů:

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í oddíly facebookUsers napříč systémovými vlákny, sečte celkový součet lajků na každém vlákně paralelně, sčítá výsledky vypočítané jednotlivými vlákny a projektuje výsledek do pěkného řetězce.

Ve formuláři diagramu:

PLINQ diagram

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í

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