Understanding C# LINQ Fundamentals and Operations

Introduction to LINQ in C#

Language Integrated Query (LINQ) is a powerful feature within the .NET framework that enables querying various data collections such as arrays, lists, and databases using a consistent syntax. Although LINQ does not define an interface itself, it operates on collections that implement either IEnumerable<T> or IQueryable<T>.

IEnumerable<T>

  • Definition: The IEnumerable<T> interface represents a generic collection that supports iteration through its elements.
  • Usage with LINQ: Any collecsion implementing IEnumerable<T> can be queried using LINQ methods like Where, Select, etc.
  • Execution Context: These operations typically execute in memory, meaning data must first be loaded into memory before processing.
  • Deferred Execution: Many LINQ operators over IEnumerable<T> use deferred execution, which means the query isn't executed until the results are enumerated.

IQueryable<T>

  • Definition: IQueryable<T> extends IEnumerable<T> and allows for query composition and deferred execution, particularly useful for database queries.
  • Usage with LINQ: Commonly used with Entity Framework or similar ORMs where queries are translated into SQL commands executed on the database server.
  • Execution Context: Queries are transformed into database-specific language (e.g., SQL) and run on the data source, optimizing performance by retrieving only needed data.
  • Deferred Execution: Similar to IEnumerable<T>, but the evaluation happens on the data source rather than in memory.

Key Differences

Classes Implementing IEnumerable<T>

  1. List: Dynamic array implementation.
  2. Arrays: All arrays implement IEnumerable<T> implicitly.
  3. Dictionary.Keys/Values: Collections of keys or values.
  4. HashSet: Collection without duplicates.
  5. Queue/Stack: Linear data structures supporting iteration.
  6. String: Characters accessible via IEnumerable<char>.

Indirect Implementation of IQueryable<T>

Typically implemented indirectly by ORM frameworks like Entity Framework:

  1. DbSet: Represents database tables in EF.
  2. Other ORM equivalents: Such as NHibernate's session queries.

Core LINQ Operations

1. Selection (Select)

Selects specific fields from a data source.

var numbers = new List<int> { 1, 2, 3, 4, 5 };
var squares = numbers.Select(n => n * n).ToList();
// Result: [1, 4, 9, 16, 25]

2. Filtering (Where)

Filters elements based on a condition.

var numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();
// Result: [2, 4]

3. Sorting (OrderBy, OrderByDescending)

Orders sequences ascending or descending.

var numbers = new List<int> { 5, 1, 4, 2, 3 };
var sortedAsc = numbers.OrderBy(n => n).ToList();
// Result: [1, 2, 3, 4, 5]

var sortedDesc = numbers.OrderByDescending(n => n).ToList();
// Result: [5, 4, 3, 2, 1]

4. Existence Checks (Any)

Determines whether any element satisfies a condition.

var numbers = new List<int> { 1, 2, 3, 4, 5 };
bool hasEven = numbers.Any(n => n % 2 == 0);
// Result: true

bool hasTen = numbers.Any(n => n == 10);
// Result: false

5. Aggregation (Count, Sum, Average, Max, Min)

Calculates aggregate values.

var sum = students.Sum(s => s.Grade);
var avg = students.Average(s => s.Age);
var maxGrade = students.Max(s => s.Grade);
var minAge = students.Min(s => s.Age);

6. Set Operations (Distinct, Intersect, Union, Except)

Manipulates sets of data.

var numbers = new List<int> { 1, 2, 2, 3, 4, 4, 5 };
var uniqueNumbers = numbers.Distinct().ToList();
// Result: [1, 2, 3, 4, 5]

7. Conversion Operations (ToArray, ToDictionary)

Converts LINQ results to different types.

var studentArray = students.ToArray();
var studentDict = students.ToDictionary(s => s.Id, s => s.Name);

8. Grouping (GroupBy)

Groups elements by a specified key.

var groupedByAge = students.GroupBy(s => s.Age).ToList();

foreach (var group in groupedByAge)
{
    Console.WriteLine($"Age: {group.Key}");
    foreach (var student in group)
    {
        Console.WriteLine($"Name: {student.Name}, Grade: {student.Grade}");
    }
}

9. Join Operations

Combines two collections based on matching keys.

Inner Join Example

var orders = new List<Order>
{
    new Order { OrderId = 1, CustomerId = 101 },
    new Order { OrderId = 2, CustomerId = 102 }
};

var customers = new List<Customer>
{
    new Customer { CustomerId = 101, Name = "Alice" },
    new Customer { CustomerId = 103, Name = "Bob" }
};

var innerJoin = orders.Join(customers,
    order => order.CustomerId,
    customer => customer.CustomerId,
    (order, customer) => new { OrderId = order.OrderId, CustomerName = customer.Name });

foreach (var item in innerJoin)
{
    Console.WriteLine($"Order ID: {item.OrderId}, Customer Name: {item.CustomerName}");
}

Left Join Example

var leftJoin = orders.GroupJoin(customers,
    order => order.CustomerId,
    customer => customer.CustomerId,
    (order, customerGroup) => new
    {
        OrderId = order.OrderId,
        CustomerName = customerGroup.DefaultIfEmpty(new Customer { Name = "(No customer)" }).First().Name
    });

foreach (var item in leftJoin)
{
    Console.WriteLine($"Order ID: {item.OrderId}, Customer Name: {item.CustomerName}");
}

10. Partitioning (Take, Skip)

Splits collections into segments.

var firstTwoStudents = students.Take(2).ToList();
var remainingStudents = students.Skip(2).ToList();

11. Paging

Implements pagination using Skip and Take.

int pageSize = 2;
int pageIndex = 1;

var pagedResults = students.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();

12. Projection (Select with Anonymous Types)

Creates new objects or anonymous types from existing ones.

var studentNames = students.Select(s => new { Name = s.Name, Age = s.Age }).ToList();

foreach (var item in studentNames)
{
    Console.WriteLine($"Name: {item.Name}, Age: {item.Age}");
}

13. Element Operations (First, Single, FirstOrDefault, SingleOrDefault)

Retrieves specific elements.

var firstStudent = students.First(s => s.Age > 20);
var firstOrDefault = students.FirstOrDefault(s => s.Age > 20);

14. Custom Query Operators

Extend LINQ functionality through extension methods.

public static class EnumerableExtensions
{
    public static IEnumerable<TSource> FilterBy<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        return source.Where(predicate);
    }
}

var filteredStudents = students.FilterBy(s => s.Age > 20);

15. Async LINQ

Support for asynchronous data processing since .NET Core 3.0.

IAsyncEnumerable<Student> GetStudentsAsync()
{
    yield return new Student { /* ... */ };
}

await foreach (var student in GetStudentsAsync())
{
    Console.WriteLine(student.Name);
}

Deferred vs Immediate Execution

LINQ queries do not execute imediately. They defer execution until the result is actually consumed, enabling efficient query chaining with out unnecessary overhead.

Tags: csharp LINQ enumerable IQueryable deferred-execution

Posted on Wed, 15 Jul 2026 17:11:32 +0000 by ++Sti++