Información general sobre LINQ
Language-Integrated Query (LINQ) proporciona funcionalidades de consulta de nivel de lenguaje y una función de orden superior API a C# y Visual Basic, que permiten escribir código declarativo expresivo.
Sintaxis de consulta de nivel de lenguaje
Esta es la sintaxis de consulta de nivel de lenguaje:
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)
Este es el mismo ejemplo mediante la API de 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 es expresivo
Imagina que tienes una lista de mascotas, pero quieres convertirla en un diccionario donde puedes acceder directamente a una mascota por su valor de RFID
.
Este es el código imperativo tradicional:
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
La intención detrás del código no es crear un nuevo Dictionary<int, Pet>
y agregarlo a través de un bucle, es convertir una lista existente en un diccionario. LINQ conserva la intención, mientras que el código imperativo no lo hace.
Esta es la expresión LINQ equivalente:
var petLookup = pets.ToDictionary(pet => pet.RFID);
Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)
El código que usa LINQ es valioso porque iguala las condiciones entre la intención y el código cuando se razona como programador. Otra bonificación es la brevedad del código. Imagine reducir grandes partes de un código base en 1/3 como se hizo anteriormente. Trato dulce, ¿verdad?
Los proveedores linq simplifican el acceso a los datos
Para un fragmento significativo de software fuera de la naturaleza, todo gira en torno a tratar con datos de algún origen (bases de datos, JSON, XML, etc.). A menudo esto implica aprender una nueva API para cada origen de datos, lo que puede ser molesto. LINQ simplifica esto mediante la abstracción de elementos comunes de acceso a datos en una sintaxis de consulta que tenga el mismo aspecto independientemente del origen de datos que elija.
Esto busca todos los elementos XML con un valor de atributo específico:
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
Escribir código para recorrer manualmente el documento XML para realizar esta tarea sería mucho más difícil.
Interactuar con XML no es lo único que puede hacer con los proveedores LINQ. LINQ to SQL es un asignador relacional de objetos (ORM) bastante básico para una base de datos del servidor MSSQL. La biblioteca Json.NET proporciona un recorrido eficaz de documentos JSON a través de LINQ. Además, si no hay una biblioteca que haga lo que necesita, también puede escribir un proveedor LINQ propio.
Motivos para usar la sintaxis de consulta
¿Por qué usar la sintaxis de consulta? Esta es una pregunta que a menudo aparece. Después de todo, el código siguiente:
var filteredItems = myItems.Where(item => item.Foo);
Dim filteredItems = myItems.Where(Function(item) item.Foo)
es mucho más conciso que esto:
var filteredItems = from item in myItems
where item.Foo
select item;
Dim filteredItems = From item In myItems
Where item.Foo
Select item
¿No es la sintaxis de api una manera más concisa de realizar la sintaxis de consulta?
No. La sintaxis de consulta permite el uso de la cláusula let, que permite introducir y enlazar una variable dentro del ámbito de la expresión, usándola en partes posteriores de la misma. La reproducción del mismo código con solo la sintaxis de api se puede realizar, pero probablemente provocará que el código sea difícil de leer.
Esto nos lleva a la pregunta: ¿debería usar la sintaxis de consulta solamente?
La respuesta a esta pregunta es sí si:
- El código base existente ya usa la sintaxis de consulta.
- Necesita delimitar el alcance de las variables dentro de las consultas debido a la complejidad.
- Prefiere la sintaxis de consulta y esta no diverge del código base.
La respuesta a esta pregunta es no si...
- El código base existente ya usa la sintaxis de la API.
- No es necesario definir el ámbito de las variables dentro de las consultas.
- Prefieres la sintaxis de la API y no te distraerá de tu base de código.
LINQ básico
Los ejemplos siguientes son una demostración rápida de algunas de las partes esenciales de LINQ. Esto no es integral, ya que LINQ proporciona más funcionalidad que lo que se muestra aquí.
El pan y la mantequilla: Where
, Select
y 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)
Acoplamiento de una lista de listas
// 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)
Unión entre dos conjuntos (con comparador personalizado)
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())
Intersección entre dos conjuntos
// 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())
Pedido
// 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)
Igualdad de las propiedades de instancia
Por último, un ejemplo más avanzado: determinar si los valores de las propiedades de dos instancias del mismo tipo son iguales (prestado y modificado de esta entrada en 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 o Parallel LINQ es un motor de ejecución paralelo para expresiones LINQ. Es decir, una expresión LINQ regular se puede paralelizar de manera sencilla en cualquier número de hilos. Esto se logra a través de una llamada a AsParallel()
anterior a la expresión.
Tenga en cuenta lo siguiente:
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)
}
Este código particionará facebookUsers
entre subprocesos del sistema según sea necesario, sumará el total de likes en cada subproceso en paralelo, sumará los resultados calculados por cada subproceso y proyectará ese resultado en una cadena agradable.
En forma de diagrama:
diagrama de
Los trabajos enlazados a CPU paralelizables que se pueden expresar fácilmente a través de LINQ (en otras palabras, son funciones puras y no tienen efectos secundarios) son un gran candidato para PLINQ. Para tareas que sí tienen efectos secundarios, considere el uso de la Biblioteca TLP.