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 likeWhere,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>extendsIEnumerable<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>
- List: Dynamic array implementation.
- Arrays: All arrays implement
IEnumerable<T>implicitly. - Dictionary.Keys/Values: Collections of keys or values.
- HashSet: Collection without duplicates.
- Queue/Stack: Linear data structures supporting iteration.
- String: Characters accessible via
IEnumerable<char>.
Indirect Implementation of IQueryable<T>
Typically implemented indirectly by ORM frameworks like Entity Framework:
- DbSet: Represents database tables in EF.
- 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.