LINQ (Language Integrated Query) is a feature in C# and other .NET languages that provides a uniform and concise syntax for querying and manipulating data. It allows developers to write SQL-like queries directly in C# code to query various types of data sources, including arrays, collections, XML, databases, and even remote data services.
LINQ was introduced in .NET Framework 3.5 and has since been a core feature in the .NET ecosystem. It integrates query capabilities directly into the language (hence the name “language integrated”), making querying a more natural and seamless part of the C# syntax.
LINQ provides the following key benefits:
There are two primary types of LINQ:
While the syntax remains the same across all these types, the implementation and execution of the query can differ depending on the data source.
LINQ supports two main styles of writing queries:
Query Syntax (also known as SQL-like syntax) Method Syntax (also known as Fluent syntax)
Query syntax resembles SQL-style queries and is typically used for readability.
var result = from num in numbers
where num % 2 == 0
orderby num descending
select num;
from
: Specifies the data source (e.g., numbers is the data source here).where
: Filters the data (returns even numbers in this case).orderby
: Sorts the data (in descending order).select
: Specifies what to return (just the numbers in this case).Method syntax uses extension methods (like Where(), Select(), OrderBy()) and is more compact and flexible, especially for complex queries.
var result = numbers.Where(num => num % 2 == 0)
.OrderByDescending(num => num)
.Select(num => num);
Where()
: Filters the collection.OrderByDescending()
: Sorts the data in descending order.Select()
: Specifies what to return.The above two queries are equivalent. The choice between query and method syntax typically comes down to readability and personal preference.
LINQ provides a rich set of methods that can be used for querying and manipulating data. Here are some common ones:
Where()
: Filters a collection based on a condition.var evenNumbers = numbers.Where(n => n % 2 == 0);
Select()
: Projects each element into a new form.var squares = numbers.Select(n => n * n);
OrderBy()
/ OrderByDescending(): Sorts the elements in ascending or descending order.var sortedNumbers = numbers.OrderBy(n => n);
GroupBy()
: Groups elements by a specified key.var groupedByLength = words.GroupBy(w => w.Length);
Distinct()
: Removes duplicate elements.var uniqueNumbers = numbers.Distinct();
First()
/ FirstOrDefault(): Returns the first element in a collection (or a default value if no match).var firstEven = numbers.First(n => n % 2 == 0);
Sum()
: Computes the sum of the elements.var total = numbers.Sum();
Any()
: Checks if any elements in the collection meet a condition.bool hasNegative = numbers.Any(n => n < 0);
All()
: Checks if all elements meet a condition.bool allPositive = numbers.All(n => n > 0);
Join()
: Joins two collections based on a common key.var joined = orders.Join(customers,
order => order.CustomerId,
customer => customer.CustomerId,
(order, customer) => new { order.OrderId, customer.Name });
One of the most important aspects of LINQ is deferred execution vs. immediate execution.
LINQ queries are not executed when they are defined but are executed when the results are actually enumerated (e.g., when you iterate over the collection).
var query = numbers.Where(n => n % 2 == 0); // Query is not executed yet
foreach (var num in query) {
Console.WriteLine(num); // Query gets executed here
}
Some LINQ methods (like ToList(), ToArray(), Count(), First(), etc.) force immediate execution of the query and return a concrete result immediately.
var list = numbers.Where(n => n % 2 == 0).ToList(); // Query is executed here
LINQ queries are often translated into commands that interact with the data source, and this is where LINQ Providers come into play. A LINQ provider is responsible for executing the query against a specific data source.
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// LINQ Query Syntax
var evenNumbersQuery = from num in numbers
where num % 2 == 0
select num;
// LINQ Method Syntax
var evenNumbersMethod = numbers.Where(num => num % 2 == 0);
Console.WriteLine("Even numbers using Query Syntax:");
foreach (var num in evenNumbersQuery)
{
Console.WriteLine(num);
}
Console.WriteLine("Even numbers using Method Syntax:");
foreach (var num in evenNumbersMethod)
{
Console.WriteLine(num);
}
}
}
In LINQ (Language Integrated Query), deferred execution means that the query is not actually executed when it is defined, but rather when the result is enumerated (i.e., when you iterate over it or explicitly force the evaluation).
Several LINQ methods support deferred execution. These methods return an IEnumerable
Here is a list of commonly used LINQ methods that support deferred execution:
Methods that support deferred execution are typically those that return an IEnumerable
Key methods that support deferred execution:
Where()
, OfType<T>()
, TakeWhile()
, SkipWhile()
Select()
, SelectMany()
OrderBy()
, OrderByDescending()
, ThenBy()
, ThenByDescending()
GroupBy()
, ToLookup()
Any()
, All()
, Count()
, Sum()
, Average()
, Min()
, Max()
, Aggregate()
Distinct()
, Union()
, Intersect()
, Except()
, Concat()
Skip()
, Take()
, Zip()
These methods allow for lazy loading of data, improving performance by delaying the actual work (like querying a database or processing a collection) until it is actually needed.
This table should give you a comprehensive overview of commonly used LINQ methods, their types, and how to use them in practice.
Method | Type | Definition | Example |
---|---|---|---|
Where() |
Filtering | Filters a sequence based on a predicate. | var evenNumbers = numbers.Where(n => n % 2 == 0); |
OfType<T>() |
Filtering | Filters elements of a sequence based on their type. | var integers = mixed.OfType<int>(); |
TakeWhile() |
Filtering | Takes elements while a condition is true. | var evenNumbers = numbers.TakeWhile(n => n % 2 == 0); |
SkipWhile() |
Filtering | Skips elements while a condition is true. | var skipped = numbers.SkipWhile(n => n % 2 != 0); |
Select() |
Projection | Projects each element of a sequence into a new form. | var squares = numbers.Select(n => n * n); |
SelectMany() |
Projection | Projects each element into a sequence and flattens the result. | var flattened = listOfLists.SelectMany(list => list); |
OrderBy() |
Sorting | Sorts elements in ascending order based on a key. | var sorted = numbers.OrderBy(n => n); |
OrderByDescending() |
Sorting | Sorts elements in descending order based on a key. | var sortedDescending = numbers.OrderByDescending(n => n); |
ThenBy() |
Sorting | Performs a secondary sort in ascending order. | var sorted = numbers.OrderBy(n => n).ThenBy(n => n.ToString()); |
ThenByDescending() |
Sorting | Performs a secondary sort in descending order. | var sorted = numbers.OrderBy(n => n).ThenByDescending(n => n); |
GroupBy() |
Grouping | Groups elements by a key. | var grouped = numbers.GroupBy(n => n % 2); |
ToLookup() |
Grouping | Creates a lookup (like a dictionary) where keys are unique. | var lookup = numbers.ToLookup(n => n % 2); |
Any() |
Aggregation | Checks if any elements satisfy a condition. | bool hasEven = numbers.Any(n => n % 2 == 0); |
All() |
Aggregation | Checks if all elements satisfy a condition. | bool allPositive = numbers.All(n => n > 0); |
Count() |
Aggregation | Returns the number of elements in a sequence or that match a condition. | var count = numbers.Count(n => n > 0); |
Sum() |
Aggregation | Computes the sum of a sequence of numeric values. | var total = numbers.Sum(); |
Average() |
Aggregation | Computes the average of a sequence of numeric values. | var average = numbers.Average(); |
Min() |
Aggregation | Returns the minimum value in a sequence. | var min = numbers.Min(); |
Max() |
Aggregation | Returns the maximum value in a sequence. | var max = numbers.Max(); |
Aggregate() |
Aggregation | Applies an accumulator function over a sequence. | var product = numbers.Aggregate((x, y) => x * y); |
Distinct() |
Set Operations | Removes duplicate elements from a sequence. | var distinctNumbers = numbers.Distinct(); |
Union() |
Set Operations | Combines two sequences and removes duplicates. | var union = numbers.Union(otherNumbers); |
Intersect() |
Set Operations | Returns the common elements between two sequences. | var common = numbers.Intersect(otherNumbers); |
Except() |
Set Operations | Returns elements from the first sequence that are not in the second. | var except = numbers.Except(otherNumbers); |
Concat() |
Set Operations | Concatenates two sequences. | var concatenated = numbers.Concat(otherNumbers); |
Skip() |
Set Operations | Skips the first n elements of a sequence. | var skipped = numbers.Skip(3); |
Take() |
Set Operations | Takes the first n elements of a sequence. | var taken = numbers.Take(3); |
Zip() |
Set Operations | Merges two sequences into one sequence by combining elements at the same position. | var zipped = numbers.Zip(otherNumbers, (n1, n2) => n1 + n2); |
First() |
Element Operations | Returns the first element in a sequence. | var first = numbers.First(); |
FirstOrDefault() |
Element Operations | Returns the first element, or a default value if no elements exist. | var firstOrDefault = numbers.FirstOrDefault(); |
Last() |
Element Operations | Returns the last element in a sequence. | var last = numbers.Last(); |
LastOrDefault() |
Element Operations | Returns the last element, or a default value if no elements exist. | var lastOrDefault = numbers.LastOrDefault(); |
Single() |
Element Operations | Returns the only element in a sequence, or throws an exception if there are multiple. | var single = numbers.Single(); |
SingleOrDefault() |
Element Operations | Returns the only element or a default value, or throws an exception if there are multiple elements. | var singleOrDefault = numbers.SingleOrDefault(); |
ElementAt() |
Element Operations | Returns the element at a specified index in a sequence. | var elementAt = numbers.ElementAt(2); |
ElementAtOrDefault() |
Element Operations | Returns the element at a specified index or a default value if index is out of range. | var elementAtOrDefault = numbers.ElementAtOrDefault(10); |
ToArray() |
Materialization | Converts a sequence to an array. | var array = numbers.ToArray(); |
ToList() |
Materialization | Converts a sequence to a List |
var list = numbers.ToList(); |
ToDictionary() |
Materialization | Converts a sequence to a dictionary. | var dictionary = numbers.ToDictionary(n => n); |
ToLookup() |
Materialization | Converts a sequence to a Lookup |
var lookup = numbers.ToLookup(n => n % 2); |
AsEnumerable() |
Materialization | Returns the sequence as IEnumerable |
var enumerable = numbers.AsEnumerable(); |
AsQueryable() |
Materialization | Returns the sequence as IQueryable |
var queryable = numbers.AsQueryable(); |
Cast<T>() |
Type Conversion | Converts a sequence to a specified type. | var casted = objects.Cast<int>(); |
ToString() |
Type Conversion | Converts a sequence to a string representation. | var str = numbers.Select(n => n.ToString()); |