Language Integrated Query (LINQ), introduced in .NET Framework 3.5 and Visual Studio 2008, provides a unified query syntax directly in C# for working with data across different sources. It operates on any collection implementing IEnumerable<T> and returns a new enumerable sequence, leveraging deferred execution in many cases.
Consider a task to find common elements between two integer arrays:
int[] seriesX = { 1, 2, 3, 4, 5, 6, 7, 8, 0 };
int[] seriesY = { 2, 4, 7, 8, 9 };
A traditional nested loop approach is verbose and imperative:
var sharedItems = new List<int>();
for (int i = 0; i < seriesX.Length; i++)
{
for (int j = 0; j < seriesY.Length; j++)
{
if (seriesX[i] == seriesY[j])
{
sharedItems.Add(seriesX[i]);
}
}
}
Using LINQ, the intent becomes declarative and concise:
var commonNumbers = from x in seriesX
from y in seriesY
where x == y
select x;
The System.Linq namespace contains the core machinery. The fundamental clause order differs from SQL: every LINQ expression starts with from to define a range varible and the source, and ends with select or group.
Key operators include:
from– declares the data source and iteration variable.where– filters elements using boolean predicates.select– projects result elements into a new shape.group– partitions results based on a key.orderby– sorts ascending or descending, with optional secondary sort keys separated by commas.joinandequals– correlate two sequences on matching keys.let– stores a computed value for reuse within the query.into– continues a query after agroup,join, orselectclause.
var highValues = from item in source
where item.Weight > 30
orderby item.Name
select new { item.Id, item.Name };
Several C# language features enhance LINQ readability:
Implicitly typed variables (var) let the compiler infer the result type, avoiding explicit IEnumerable<T> declarations:
var filtered = from e in entries where e.Active select e.Title;
Anonymous types enable on-the-fly projections without defining a separate class:
var shaped = from p in products
where p.Price > 50
select new { p.Sku, p.DisplayName };
Query Patterns
Basic Projection
var titles = from doc in documents select doc.Title;
Filtered Retrieval
var cheapItems = from item in inventory
where item.Cost < 10
select item;
Grouping
var categoryGroups = from prod in products
group prod by prod.Category into grouped
orderby grouped.Key
select grouped;
Inner Join
var joinedData = from cust in customers
join ord in orders on cust.Id equals ord.CustomerId
select new { CustomerName = cust.Name, OrderId = ord.Id };
Left Outer Join
var leftJoinResult = from emp in employees
join dept in departments on emp.DeptId equals dept.Id into deptGroup
from d in deptGroup.DefaultIfEmpty()
select new { EmployeeName = emp.Name, Department = d?.Name };
The DefaultIfEmpty() method supplies a default element when the right side has no matching records.
Mastering these patterns enables effetcive use of LINQ across objects, databases, and XML, far beyond the limited association with LINQ to SQL.