Consultar una colección de objetos

El término "LINQ to Objects" se refiere al uso de consultas LINQ con cualquier colección IEnumerable o IEnumerable<T> directamente, sin usar un proveedor o una API de LINQ intermedios como LINQ to SQL o LINQ to XML. Puede usar LINQ para consultar cualquier colección enumerable, como List<T>, Array o Dictionary<TKey,TValue>. La colección puede haberla definido el usuario, o bien puede que la haya devuelto una API de .NET. En el enfoque de LINQ, se escribe código declarativo que describe qué se quiere recuperar.

Además, las consultas LINQ ofrecen tres ventajas principales respecto a los bucles foreach tradicionales:

  • Son más concisas y legibles, especialmente cuando se filtran varias condiciones.
  • Proporcionan funcionalidades eficaces para filtrar, ordenar y agrupar con un código de aplicación mínimo.
  • Se pueden migrar a otros orígenes de datos con muy poca o ninguna modificación.

Por lo general, cuanto más compleja es la operación que se quiere realizar en los datos, más ventajas se obtienen al usar LINQ en lugar de las técnicas de iteración tradicionales.

En este ejemplo se muestra cómo realizar una consulta simple en una lista de objetos Student. Cada objeto Student contiene información básica sobre el alumno y una lista que representa las puntuaciones del alumno en cuatro exámenes.

Nota

Muchos otros ejemplos de esta sección usan la misma clase Student y colección students.

class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int ID { get; set; }
    public GradeLevel? Year { get; set; }
    public List<int> ExamScores { get; set; }

    public Student(string FirstName, string LastName, int ID, GradeLevel Year, List<int> ExamScores)
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
        this.ID = ID;
        this.Year = Year;
        this.ExamScores = ExamScores;
    }

    public Student(string FirstName, string LastName, int StudentID, List<int>? ExamScores = null)
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
        ID = StudentID;
        this.ExamScores = ExamScores ?? [];
    }

    public static List<Student> students =
    [
        new(
            FirstName: "Terry", LastName: "Adams", ID: 120,
            Year: GradeLevel.SecondYear,
            ExamScores: [99, 82, 81, 79]
        ),
        new(
            "Fadi", "Fakhouri", 116,
            GradeLevel.ThirdYear,
            [99, 86, 90, 94]
        ),
        new(
            "Hanying", "Feng", 117,
            GradeLevel.FirstYear,
            [93, 92, 80, 87]
        ),
        new(
            "Cesar", "Garcia", 114,
            GradeLevel.FourthYear,
            [97, 89, 85, 82]
        ),
        new(
            "Debra", "Garcia", 115,
            GradeLevel.ThirdYear,
            [35, 72, 91, 70]
        ),
        new(
            "Hugo", "Garcia", 118,
            GradeLevel.SecondYear,
            [92, 90, 83, 78]
        ),
        new(
            "Sven", "Mortensen", 113,
            GradeLevel.FirstYear,
            [88, 94, 65, 91]
        ),
        new(
            "Claire", "O'Donnell", 112,
            GradeLevel.FourthYear,
            [75, 84, 91, 39]
        ),
        new(
            "Svetlana", "Omelchenko", 111,
            GradeLevel.SecondYear,
            [97, 92, 81, 60]
        ),
        new(
            "Lance", "Tucker", 119,
            GradeLevel.ThirdYear,
            [68, 79, 88, 92]
        ),
        new(
            "Michael", "Tucker", 122,
            GradeLevel.FirstYear,
            [94, 92, 91, 91]
        ),
        new(
            "Eugene", "Zabokritski", 121,
            GradeLevel.FourthYear,
            [96, 85, 91, 60]
        )
    ];
}

enum GradeLevel
{
    FirstYear = 1,
    SecondYear,
    ThirdYear,
    FourthYear
};

Ejemplo

La consulta siguiente devuelve los alumnos que reciben una puntuación de 90 o más en su primer examen.

void QueryHighScores(int exam, int score)
{
    var highScores =
        from student in students
        where student.ExamScores[exam] > score
        select new
        {
            Name = student.FirstName,
            Score = student.ExamScores[exam]
        };

    foreach (var item in highScores)
    {
        Console.WriteLine($"{item.Name,-15}{item.Score}");
    }
}

QueryHighScores(0, 90);

Esta consulta es deliberadamente simple para permitirle experimentar. Por ejemplo, puede probar otras condiciones en la cláusula where o usar una cláusula orderby para ordenar los resultados.

Clasificación de operadores de consulta estándar por modo de ejecución

Las implementaciones de LINQ to Objects de los métodos de operador de consulta estándar se ejecutan de una de dos formas principales: inmediata o aplazada. Los operadores de consulta que usan la ejecución aplazada pueden dividirse además en dos categorías: de streaming y de no streaming. Si sabe cómo se ejecutan los diferentes operadores de consulta, puede servirle para entender los resultados que se obtienen de una consulta determinada. Esto es especialmente cierto si se está cambiando el origen de datos o si se está creando una consulta sobre otra. En este tema se clasifican los operadores de consulta estándar según su modo de ejecución.

Inmediato

La ejecución inmediata significa que se lee el origen de datos y que la operación se realiza una vez. Todos los operadores de consulta estándar que devuelven un resultado escalar se ejecutan de manera inmediata. Puede forzar que una consulta se ejecute inmediatamente mediante los métodos Enumerable.ToList y Enumerable.ToArray. La ejecución inmediata permite reutilizar los resultados de la consulta, no su declaración. Los resultados se recuperan una vez y, después, se almacenan para usarlos en el futuro.

Aplazada

La ejecución aplazada significa que la operación no se realiza en el punto en el código donde se declara la consulta. La operación se realiza solo cuando se enumera la variable de consulta, por ejemplo, mediante una instrucción foreach. Esto significa que los resultados de ejecutar la consulta dependen del contenido del origen de datos cuando se ejecuta la consulta en lugar de cuando se define la consulta. Si la variable de consulta se enumera varias veces, es posible que los resultados difieran cada vez. Casi todos los operadores de consulta estándar cuyo tipo de valor devuelto es IEnumerable<T> o IOrderedEnumerable<TElement> se ejecutan de una manera diferida. La ejecución diferida aporta la facilidad de reutilizar las consultas, ya que la consulta captura los datos actualizados del origen de datos cada vez que se iteran los resultados de la consulta.

Los operadores de consulta que usan la ejecución aplazada pueden clasificarse además como de streaming o de no streaming.

Streaming

Los operadores de streaming no deben leer todos los datos de origen antes de que produzcan elementos. En el momento de la ejecución, un operador de streaming realiza su operación en cada elemento de origen mientras se lee y proporciona el elemento si es necesario. Un operador de streaming continúa leyendo los elementos de origen hasta que se puede generar un elemento de resultado. Esto significa que es posible leer más de un elemento de origen para generar un elemento de resultado.

No son de streaming

Los operadores de no streaming deben leer todos los datos de origen antes de poder proporcionar un elemento de resultado. Las operaciones como la ordenación o la agrupación pertenecen a esta categoría. En tiempo de ejecución, los operadores de consulta de no streaming leen todos los datos de origen, los colocan en una estructura de datos, realizan la operación y proporcionan los elementos resultantes.

Tabla de clasificación

En la tabla siguiente se clasifica cada método de operador de consulta estándar según su método de ejecución.

Nota:

Si un operador se marca en dos columnas, dos secuencias de entrada intervienen en la operación, y cada secuencia se evalúa de manera diferente. En estos casos, siempre es la primera secuencia de la lista de parámetros la que se evalúa en un modo de transmisión diferido.

Operador de consulta estándar Tipo de valor devuelto Ejecución inmediata Ejecución aplazada de streaming Ejecución aplazada de no streaming
Aggregate TSource x
All Boolean X
Any Boolean X
AsEnumerable IEnumerable<T> X
Average Valor numérico único x
Cast IEnumerable<T> X
Concat IEnumerable<T> X
Contains Boolean X
Count Int32 X
DefaultIfEmpty IEnumerable<T> X
Distinct IEnumerable<T> X
ElementAt TSource X
ElementAtOrDefault TSource? X
Empty IEnumerable<T> X
Except IEnumerable<T> X X
First TSource X
FirstOrDefault TSource? X
GroupBy IEnumerable<T> X
GroupJoin IEnumerable<T> X X
Intersect IEnumerable<T> X X
Join IEnumerable<T> X X
Last TSource X
LastOrDefault TSource? X
LongCount Int64 X
Max Valor numérico único, TSource o TResult? x
Min Valor numérico único, TSource o TResult? X
OfType IEnumerable<T> X
OrderBy IOrderedEnumerable<TElement> X
OrderByDescending IOrderedEnumerable<TElement> X
Range IEnumerable<T> X
Repeat IEnumerable<T> X
Reverse IEnumerable<T> X
Select IEnumerable<T> X
SelectMany IEnumerable<T> X
SequenceEqual Boolean X
Single TSource X
SingleOrDefault TSource? X
Skip IEnumerable<T> X
SkipWhile IEnumerable<T> X
Sum Valor numérico único x
Take IEnumerable<T> X
TakeWhile IEnumerable<T> X
ThenBy IOrderedEnumerable<TElement> X
ThenByDescending IOrderedEnumerable<TElement> X
ToArray Matriz TSource[] x
ToDictionary Dictionary<TKey,TValue> X
ToList IList<T> X
ToLookup ILookup<TKey,TElement> X
Union IEnumerable<T> X
Where IEnumerable<T> X

Vea también