C# LINQ Extension Methods and Functional Programming Patterns

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 type TResult.

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.

Tags: C# LINQ IEnumerable Func Action

Posted on Tue, 02 Jun 2026 16:39:51 +0000 by nitation