Přehled LINQ

Language-Integrated Query (LINQ) poskytuje možnosti dotazování na úrovni jazyka a rozhraní API funkce vyššího řádu pro C# a Visual Basic, které umožňuje psát expresivní 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 s použitím 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ýrazový

Imagine 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ý a přidat do něj smyčku . Je to převedení existujícího seznamu Dictionary<int, Pet> 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, který používá LINQ, je užitečný, protože při uvažování jako programátor dokonce hraje roli mezi záměrem a kódem. Dalším bonusem je stručnost kódu. Imagine 1/3 tak, jak jsme to udělali výše, zmenšuje velké části kódu o 1/3. Sytá dohoda, že?

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

V případě významného bloku softwaru v divočině se všechno točí kolem práce s daty z nějakého zdroje (databáze, JSON, XML atd.). Často to zahrnuje 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, 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í přecházování dokumentu XML pro tuto úlohu by bylo mnohem náročnější.

Interakce s XML není jediná věc, kterou můžete dělat se zprostředkovateli LINQ. Linq to SQL je poměrně homádná Object-Relational Mapper (ORM) pro serverovou databázi 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 zprostředkovatele LINQ!

Důvody použití syntaxe dotazu

Proč používat syntaxi dotazů? Jedná se o otázku, která často nastává. 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 pouze stručnějším způsobem, jak provést syntaxi dotazu?

No. Syntaxe dotazu umožňuje použití klauzule let, která umožňuje zavést a svázat proměnnou v rámci oboru výrazu a použít ji v dalších částech výrazu. Stejný kód je možné reprodukovat pouze pomocí syntaxe rozhraní API, ale s největší pravděpodobností to povede ke kódu, který se těžko čte.

Takže toto je ta otázka, měli byste použít syntaxi dotazu?

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

  • Váš stávající kód již používá syntaxi dotazu.
  • Kvůli složitosti musíte v dotazech vymezit obor proměnných.
  • Dáváte přednost syntaxi dotazu a nebude vás rušit od vašeho kódu.

Odpověď na tuto otázku je ne, pokud...

  • Váš stávající kód již používá syntaxi rozhraní API.
  • Proměnné v dotazech není nutné vymeovat.
  • Dáváte přednost syntaxi rozhraní API a nebude vás rušit od vašeho kódu.

Základní LINQ

Skutečně komplexní seznam ukázek LINQ najdete na stránce 101 Ukázek LINQ.

Následující příklady jsou rychlou ukázkou některých základních částí LINQ. To není v žádném případě vyčerpávající, protože LINQ poskytuje více funkcí, než je zde dispozici.

Chřát a Where šmach – Select , a 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)

Sjednotování 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

Nakonec pokročilejší ukázka: určení, jestli se hodnoty vlastností dvou instancí stejného typu rovnají (vypůjčené a upravené 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 také 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. Toho se dosahuje voláním před AsParallel() výrazem.

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 bude podle potřeby rozdělovat napříč systémovými vlákny, sečte celkový počet lajků v každém vlákně paralelně, sečte výsledky vypočítané jednotlivými vlákny a promítne výsledek do facebookUsers vhodného řetězce.

Ve formuláři 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ší efekt, zvažte použití knihovny Task Parallel Library.

Další zdroje informací

  • Ukázky LINQ 101
  • Linqpad– prostředí testovacího prostředí a databázový dotazovací modul pro C#/F#/Visual Basic
  • EduLinq– elektronická kniha pro učení implementace LINQ-to-objects