LINQ (Language Integrated Query) enables expressive, declarative data querying over collections using method syntax or query expressions. Below is a structured overview of core LINQ patterns and their practical usage.
Functional Delegates: Action and Func
C# provides built-in generic delegates to represent functions without defining custom delegate types:
Action<T1, ..., T16>— Represents a method with 0 to 16 input parameters and no return value.Func<T1, ..., T16, TResult>— Represents a method with 0 to 16 inputs and a return typeTResult.
These are commonly used with lambda expressions for concise functon definition:
Action<string> log = s => Console.WriteLine($"Log: {s}");
Func<int, int, double> average = (a, b) => (a + b) / 2.0;
Filtering and Selection
Where() filters elements based on a predicate condition:
var highEarners = employees.Where(emp => emp.Salary > 100);
Count() returns the number of matching elements:
int seniorCount = employees.Count(emp => emp.Age >= 30);
Any() checks if at least one element satisfies the condition:
bool hasHighSalary = employees.Any(emp => emp.Salary > 500);
Element Retrieval
Different methods handle element retrieval with varying error semantics:
| Method | Behavior |
|---|---|
Single() |
Throws if zero or multiple matches exist |
SingleOrDefault() |
Returns default(T) if no match; throws if multiple |
First() |
Throws if no match found |
FirstOrDefault() |
Returns default(T) if no match; first match otherwise |
Sorting and Ordering
Elements can be sorted in ascending or descending order:
var sortedByName = employees.OrderBy(emp => emp.Name);
var sortedBySalaryDesc = employees.OrderByDescending(emp => emp.Salary);
Multi-level sorting is achieved using ThenBy() and ThenByDescending():
var ordered = employees
.OrderBy(emp => emp.Age)
.ThenByDescending(emp => emp.Salary);
Custom sorting logic is also possible:
- Sort by last character of a string property:
var sortedByLastChar = items.OrderBy(item => item.Name[item.Name.Length - 1]);
- Randomize order using GUIDs:
var shuffled = employees.OrderBy(emp => Guid.NewGuid());
Limiting and Skipping Results
Use Skip(n) to bypass the first n elements and Take(n) to extract the next n:
var page = employees.Skip(5).Take(10); // 6th to 15th items
Aggregation Functions
Standard statistical operations are available:
| Method | Description |
|---|---|
Max() |
Returns the highest value |
Min() |
Returns the lowest value |
Average() |
Computes mean |
Sum() |
Total of all value |
Count() |
Number of elements |
Grouping Data
GroupBy() groups elements by a key and returns IGrouping<TKey, TSource>, which inherits from IEnumerable<TSource>. After grouping, subsequent operations apply to groups, not individual items.
var groupedByAge = employees
.GroupBy(emp => emp.Age)
.Select(g => new
{
Age = g.Key,
Total = g.Count(),
AvgSalary = g.Average(e => e.Salary),
MaxSalary = g.Max(e => e.Salary)
});
To find the highest-paid employee per group:
var topEarnersPerAge = employees
.GroupBy(emp => emp.Age)
.Select(g => g.OrderByDescending(e => e.Salary).First());
Alternatively, collect all employees tied for the highest salary per group:
var maxSalaryPerGroup = employees
.GroupBy(emp => emp.Age)
.Select(g => new
{
Age = g.Key,
TopEmployees = g.Where(e => e.Salary == g.Max(x => x.Salary)).ToList()
});
Projection with Select
Select() transforms each element into a new form:
var namesAndGenders = employees.Select(emp => new
{
Name = emp.Name,
Gender = emp.Gender ? "Male" : "Female"
});
var ageList = employees.Select(emp => emp.Age);
Projection can also combine multiple aggregations:
var ageStats = employees
.GroupBy(emp => emp.Age)
.Select(g => new
{
AgeRange = g.Key,
EmployeeCount = g.Count(),
HighestPaid = g.Max(e => e.Salary),
LowestPaid = g.Min(e => e.Salary),
TopCandidates = g.Where(e => e.Salary == g.Max(x => x.Salary))
});
Collection Conversion
Convert IEnumerable<T> results to concrete collections:
Emp[] array = employees.Where(e => e.Name.Contains("a")).ToArray();
List<Emp> list = employees.Where(e => e.Salary > 100).ToList();
Chaining Operations
Since most LINQ methods return IEnumerable<T>, they support fluent chaining:
var result = employees
.Where(emp => emp.Id.CompareTo("2") > 0)
.GroupBy(emp => emp.Age)
.OrderBy(group => group.Key)
.Take(3)
.Select(group => new
{
Age = group.Key,
Count = group.Count(),
AverageSalary = group.Average(e => e.Salary)
});
This pattern enables readable, maintainable data pipelines with minimal intermediate variables.