LINQ (Language Integrated Query)LINQ (Language Integrated Query)

DescrizioneWhat is it?

LINQ fornisce funzionalità di query a livello di linguaggio e un'API di funzione di ordine superiore a C# e Visual Basic come modo per scrivere codice dichiarativo espressivo.LINQ provides language-level querying capabilities and a higher-order function API to C# and Visual Basic as a way to write expressive, declarative code.

Sintassi di query a livello di linguaggio:Language-level query syntax:

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)

Stesso esempio usando l'API IEnumerable<T>:Same example using the 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 è espressivoLINQ is Expressive

Si supponga di avere un elenco di animali domestici e di volerlo convertire in un dizionario nel quale sia possibile accedere a un animale selezionando direttamente il valore RFID corrispondente.Imagine you have a list of pets, but want to convert it into a dictionary where you can access a pet directly by its RFID value.

Codice imperativo tradizionale:Traditional imperative 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

Scopo del codice non è creare un nuovo Dictionary<int, Pet> e aggiungerlo tramite un ciclo. Piuttosto è convertire un elenco esistente in un dizionario.The intention behind the code is not to create a new Dictionary<int, Pet> and add to it via a loop, it is to convert an existing list into a dictionary! A differenza del codice imperativo, LINQ consente di rispettare tale intenzione.LINQ preserves the intention whereas the imperative code does not.

Espressione LINQ equivalente:Equivalent LINQ expression:

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

Il codice che usa LINQ è utile perché mette sullo stesso livello scopo e codice in fase di programmazione.The code using LINQ is valuable because it evens the playing field between intent and code when reasoning as a programmer. Un altro vantaggio del codice è il fatto che sia conciso.Another bonus is code brevity. È infatti possibile ridurre parti prolisse della codebase di un 1/3 come illustrato in precedenza.Imagine reducing large portions of a codebase by 1/3 as done above. Interessante, vero?Pretty sweet deal, right?

I provider LINQ semplificano l'accesso ai datiLINQ Providers Simplify Data Access

Per gran parte dei software in circostanze normali, tutto è basato sulla gestione dei dati da un'origine dati, ad esempio database, JSON, XML e così via.For a significant chunk of software out in the wild, everything revolves around dealing with data from some source (Databases, JSON, XML, etc). Spesso è necessario imparare a conoscere un'API nuova per ogni origine dati, operazione alquanto noiosa.Often this involves learning a new API for each data source, which can be annoying. LINQ semplifica tale processo estraendo gli elementi comuni di accesso ai dati in una sintassi di query simile indipendentemente dal tipo di origine dei dati raccolti.LINQ simplifies this by abstracting common elements of data access into a query syntax which looks the same no matter which data source you pick.

Nell'esempio seguente vengono individuati tutti gli elementi XML con un valore dell'attributo specifico.Consider the following: finding all XML elements with a specific attribute value.

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

Scrivere il codice per attraversare manualmente il documento XML ed eseguire tale operazione sarebbe più complicato.Writing code to manually traverse the XML document to perform this task would be far more challenging.

I provider LINQ non consentono solo di interagire con XML.Interacting with XML isn’t the only thing you can do with LINQ Providers. LINQ to SQL è un Object-Relational Mapper (ORM) pressoché essenziale per un database del server MSSQL.Linq to SQL is a fairly bare-bones Object-Relational Mapper (ORM) for an MSSQL Server Database. La libreria JSON.NET consente l'attraversamento di un documento JSON tramite LINQ.The JSON.NET library provides efficient JSON Document traversal via LINQ. Se invece non esiste una libreria adatta, è anche possibile scrivere un provider LINQ personalizzato.Furthermore, if there isn’t a library which does what you need, you can also write your own LINQ Provider!

Perché usare la sintassi di query?Why Use the Query Syntax?

È una domanda frequente.This is a question which often comes up. Dopo tutto, questa sintassiAfter all, this,

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

è molto più breve rispetto alla seguente:is a lot more concise than this:

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

La sintassi dell'API non è un modo più conciso per una sintassi di query?Isn’t the API syntax just a more concise way to do the query syntax?

No.No. La sintassi di query consente l'uso della clausola let grazie alla quale è possibile introdurre e associare una variabile nell'ambito dell'espressione, usandola nelle parti successive dell'espressione.The query syntax allows for the use of the let clause, which allows you to introduce and bind a variable within the scope of the expression, using it in subsequent pieces of the expression. È possibile riprodurre lo stesso codice usando solo la sintassi dell'API, ma il codice risulterebbe di difficile lettura.Reproducing the same code with only the API syntax can be done, but will most likely lead to code which is hard to read.

Una domanda sorge spontanea. È consigliabile usare solo la sintassi di query?So this begs the question, should you just use the query syntax?

La risposta a questa domanda è se...The answer to this question is yes if...

  • La codebase esistente usa già la sintassi di queryYour existing codebase already uses the query syntax
  • È necessario definire l'ambito delle variabili all'interno delle query per motivi di complessitàYou need to scope variables within your queries due to complexity
  • Si preferisce la sintassi di query, che non si distaccherà dalla codebaseYou prefer the query syntax and it won’t distract from your codebase

La risposta a questa domanda è no se...The answer to this question is no if...

  • La codebase esistente usa già la sintassi dell'APIYour existing codebase already uses the API syntax
  • Non è necessario definire l'ambito delle variabili all'interno delle queryYou have no need to scope variables within your queries
  • Si preferisce la sintassi dell'API, che non si distaccherà dalla codebaseYou prefer the API syntax and it won’t distract from your codebase

Esempi significativiEssential Samples

Per un elenco completo di esempi di LINQ, vedere 101 LINQ Samples (101 esempi di LINQ).For a truly comprehensive list of LINQ samples, visit 101 LINQ Samples.

Di seguito una rapida dimostrazione di alcune delle parti essenziali di LINQ.The following is a quick demonstration of some of the essential pieces of LINQ. Non è affatto una dimostrazione completa, poiché LINQ offre molte più funzionalità rispetto a quanto viene illustrato di seguito.This is in no way comprehensive, as LINQ provides significantly more functionality than what is showcased here.

  • Essenziali sono Where, Select e Aggregate:The bread and butter - Where, Select, and Aggregate:
// Filtering a list.
var germanShepards = dogs.Where(dog => dog.Breed == DogBreed.GermanShepard);

// Using the query syntax.
var queryGermanShepards = from dog in dogs
                          where dog.Breed == DogBreed.GermanShepard
                          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 germanShepards = dogs.Where(Function(dog) dog.Breed = DogBreed.GermanShepard)

' Using the query syntax.
Dim queryGermanShepards = From dog In dogs
                          Where dog.Breed = DogBreed.GermanShepard
                          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)
  • Elenco di elenchi bidimensionale:Flattening a list of lists:
// 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)
  • Unione tra due set, con criteri di confronto personalizzati:Union between two sets (with custom 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())
  • Intersezione tra due set:Intersection between two 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())
  • Ordinamento:Ordering:
// 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)
  • Infine, un esempio più avanzato: determinare se i valori delle proprietà di due istanze dello stesso tipo sono uguali. L'esempio è stato preso in prestito e modificato da questo articolo di StackOverflow:Finally, a more advanced sample: determining if the values of the properties of two instances of the same type are equal (Borrowed and modified from this 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

PLINQPLINQ

PLINQ o Parallel LINQ è un motore di esecuzione parallela per le espressioni LINQ.PLINQ, or Parallel LINQ, is a parallel execution engine for LINQ expressions. In altre parole, un'espressione LINQ può essere facilmente parallelizzata su un numero qualsiasi di thread.In other words, a regular LINQ expression can be trivially parallelized across any number of threads. Questa operazione viene eseguita chiamando AsParallel(), che precede l'espressione.This is accomplished via a call to AsParallel() preceding the expression.

Tenere presente quanto riportato di seguito:Consider the following:

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

Questo codice, se necessario, crea un partizione di facebookUsers tra i thread del sistema, somma i like totali su ogni thread in parallelo, somma i risultati calcolati da ogni thread e proietta il risultato in una stringa.This code will partition facebookUsers across system threads as necessary, sum up the total likes on each thread in parallel, sum the results computed by each thread, and project that result into a nice string.

Sotto forma di diagramma:In diagram form:

Diagramma PLINQ

I processi basati su CPU parallelizzabili che possono essere espressi facilmente tramite LINQ, quindi funzioni pure senza effetti collateri, sono i candidati ideali di PLINQ.Parallelizable CPU-bound jobs which can be easily expressed via LINQ (in other words, are pure functions and have no side effects) are a great candidate for PLINQ. Per i processi con effetti collaterali, è possibile usare Task Parallel Library.For jobs which do have a side effect, consider using the Task Parallel Library.

Altre risorse:Further Resources:

  • 101 esempi di LINQ101 LINQ Samples
  • LINQPad, un ambiente Playground e un motore di query di C#databaseF#per//Visual BasicLinqpad, a playground environment and Database querying engine for C#/F#/Visual Basic
  • EduLinq, un e-book per scoprire come implementare LINQ-to-objectsEduLinq, an e-book for learning how LINQ-to-objects is implemented