C# LINQ

C Sharp Programming Tutorials


Language Integrated Query, or LINQ, gives C# a consistent way to filter, transform, sort, group, join, and summarize data. The same query concepts work with in-memory collections and with providers that translate expression trees for databases or other sources.

Import System.Linq when needed. A LINQ query describes the result you want; the source and chosen provider determine how it executes.

Create a LINQ Query

Most sequence operators accept lambda expressions. Chain operators in the order that data should flow through them.

Example:

using System.Linq;

int[] scores = [72, 91, 84, 67, 95];

var highScores = scores
    .Where(score => score >= 80) // Keep matching values.
    .OrderByDescending(score => score);

foreach (int score in highScores)
{
    Console.WriteLine(score);
}

Method Syntax and Query Syntax

C# supports method syntax and SQL-like query syntax. The compiler translates query expressions into method calls. Some operations, including Count() and Max(), have no query-clause form.

string[] names = ["Aarav", "Olivia", "Harry", "Mia"];

// Query syntax.
var queryForm =
    from name in names
    where name.Length >= 5
    orderby name
    select name.ToUpperInvariant();

// Equivalent method syntax.
var methodForm = names
    .Where(name => name.Length >= 5)
    .OrderBy(name => name)
    .Select(name => name.ToUpperInvariant());
Operation Common operators
Filtering Where, OfType
Projection Select, SelectMany
Ordering OrderBy, ThenBy, Reverse
Grouping GroupBy, ToLookup
Joining Join, GroupJoin
Aggregation Count, Sum, Average, Min, Max, Aggregate
Element selection First, Single, ElementAt and OrDefault variants

Project Results with Select

Projection changes the shape of each result. Create an anonymous object when downstream code needs only selected values.

record Product(int Id, string Name, decimal Price);

Product[] products =
[
    new(1, "Keyboard", 3200m),
    new(2, "Mouse", 1400m)
];

var cards = products.Select(product => new
{
    product.Name,
    // Calculate a display value without changing the source.
    PriceWithTax = product.Price * 1.18m
});

Flatten Data with SelectMany

SelectMany() maps each source item to a sequence and flattens those sequences into one result.

string[][] teams =
[
    ["Aarav", "Mia"],
    ["Olivia", "Harry"]
];

// Produce one sequence containing every team member.
IEnumerable<string> members = teams.SelectMany(team => team);

Sort by Multiple Keys

Start with OrderBy() or OrderByDescending(). Add secondary keys with ThenBy() or ThenByDescending().

var ordered = products
    .OrderBy(product => product.Price)
    .ThenBy(product => product.Name); // Break equal-price ties.

Group Values

GroupBy() creates groups identified by a key. Each group is enumerable and exposes its key.

record Employee(string Name, string Department, decimal Salary);

Employee[] employees =
[
    new("Aarav", "Engineering", 92000m),
    new("Olivia", "Design", 81000m),
    new("Harry", "Engineering", 88000m)
];

var totals = employees
    .GroupBy(employee => employee.Department)
    .Select(group => new
    {
        Department = group.Key,
        Count = group.Count(),
        TotalSalary = group.Sum(employee => employee.Salary)
    });

Join Two Sequences

Join() performs an inner equijoin. It matches outer and inner keys, then uses a result selector to create each output value.

record Department(int Id, string Name);
record Staff(string Name, int DepartmentId);

Department[] departments = [new(1, "Sales"), new(2, "Support")];
Staff[] staff = [new("Mia", 2), new("Noah", 1)];

var directory = staff.Join(
    departments,
    person => person.DepartmentId, // Outer key.
    department => department.Id,   // Inner key.
    (person, department) => new { person.Name, Department = department.Name }
);

Understand Deferred Execution

Many operators that return IEnumerable<T> do not read the source immediately. They run when you enumerate the query. If the source changes before enumeration, the results may change too.

List<int> numbers = [1, 2, 3, 4];
var evenNumbers = numbers.Where(number => number % 2 == 0);

numbers.Add(6);
// Enumeration happens here, so the result includes 6.
Console.WriteLine(string.Join(", ", evenNumbers));

Call ToList() or ToArray() when you need a snapshot or want to avoid executing an expensive query more than once.

Choose Safe Element Operators

First() requires at least one element. Single() requires exactly one. Their OrDefault variants return a default value when no element exists, but SingleOrDefault() still throws when more than one item matches.

Do not hide an unexpected duplicate by replacing Single() with First(). Choose the operator that expresses the data rule, then handle the possible empty result clearly.

LINQ Providers and Translation

IEnumerable<T> queries run as .NET code over objects. IQueryable<T> providers inspect expression trees and may translate supported operations to another language such as SQL. A method that works in LINQ to Objects may not be translatable by every provider.

Common LINQ Mistakes

  • Enumerating the same expensive deferred query repeatedly.
  • Calling ToList() too early and moving filtering from a provider to memory.
  • Using Count() only to test for data instead of Any().
  • Forgetting that OrderBy starts a new ordering and ThenBy extends it.
  • Adding side effects inside predicates or projections.
  • Assuming every .NET method can be translated by an IQueryable provider.

Conclusion

C# LINQ composes clear operations for filtering, projection, sorting, grouping, joining, and aggregation. Understand deferred execution, materialize results intentionally, select element operators that match your rules, and consider provider translation when the query runs outside an in-memory collection.



Found This Page Useful? Share It!
Get the Latest Tutorials and Updates
Join us on Telegram