LINQ
LINQ (Language Integrated Query) is a set of language and framework features that bring query capabilities into C#.
LINQ queries work on any data source that supports a suitable provider.
Common providers include:
- LINQ to Objects – for in-memory collections (List
, Array, etc.) - LINQ to XML – for XML documents (XDocument, XElement)
- LINQ to Entities – for querying databases through Entity Framework
- LINQ to DataSet – for ADO.NET DataSets
Query Syntax vs Method Syntax
C# supports two syntaxes for writing LINQ queries:
- Query syntax (SQL-like)
- Method syntax (fluent chain of extension methods)
Query Syntax Example
var result = from n in numbers
where n > 2
orderby n descending
select n;
Method Syntax Example
var result = numbers
.Where(n => n > 2)
.OrderByDescending(n => n);
LINQ Operators
Where
Filters elements in a sequence based on a specified condition.
// Query Syntax
var filtered = from num in numbers
where num > 5
select num;
// Method Syntax
var filtered = numbers.Where(num => num > 5);
OfType
Filters elements in a sequence to include only those of a specified type.
object[] mixedArray = { 1, "hello", 2, "world", 3.14 };
var strings = mixedArray.OfType<string>(); // ["hello", "world"]
OrderBy / OrderByDescending
These are used to sort a collection by a specified key in ascending or descending order.
var sortedAsc = students.OrderBy(s => s.Age);
var sortedDesc = students.OrderByDescending(s => s.Age);
ThenBy / ThenByDescending
These are are used for secondary sorting when multiple sort criteria are applied.
var sorted = students.OrderBy(s => s.LastName)
.ThenBy(s => s.FirstName);
GroupBy
GroupBy in LINQ groups elements in a collection based on a specified key.
var words = new[] { "apple", "banana", "apricot", "blueberry" };
var groups = words.GroupBy(w => w[0]);
foreach (var g in groups)
Console.WriteLine($"{g.Key}: {string.Join(", ", g)}");
// output
a: apple, apricot
b: banana, blueberry
First / FirstOrDefault
First() gets the first matching element and throws an error if none found. FirstOrDefault() safely returns the first matching element or a default value if none exists.
var numbers = new int[] { 1, 2, 3 };
var first = numbers.FirstOrDefault(); // 1
var empty = new int[] { };
var safe = empty.FirstOrDefault(); // 0 (default of int)
Join / Group Join
join in LINQ combines two collections based on a matching key (like an SQL INNER JOIN), while group join groups related elements from the second collection into each element of the first (like an SQL LEFT JOIN).
var students = new[]
{
new { Name = "Ali", ClassId = 1 },
new { Name = "Sara", ClassId = 2 }
};
var classes = new[]
{
new { ClassId = 1, Title = "Math" },
new { ClassId = 2, Title = "Physics" }
};
// join
var joinResult = from s in students
join c in classes on s.ClassId equals c.ClassId
select new { s.Name, c.Title };
// group join
var groupJoinResult = from c in classes
join s in students on c.ClassId equals s.ClassId into studentGroup
select new { c.Title, Students = studentGroup };
Concat / Distinct
Concat() joins two sequences end-to-end without removing duplicates, while Distinct() returns only unique elements from a sequence.
var list1 = new[] { 1, 2, 3 };
var list2 = new[] { 3, 4, 5 };
var result = list1.Concat(list2).Distinct();
Console.WriteLine(string.Join(", ", result)); // 1, 2, 3, 4, 5
Zip
Zip() combines two sequences element by element into a single sequence of pairs (or any custom result you define).
var names = new[] { "Ali", "Sara", "Nima" };
var scores = new[] { 90, 85, 95 };
var students = names.Zip(scores, (name, score) => new { name, score });
foreach (var s in students)
Console.WriteLine($"{s.name}: {s.score}");
Skip / Take
Skip() ignores a number of elements from the start, and Take() returns a limited number of elements — together they’re perfect for paging or slicing sequences.
var numbers = Enumerable.Range(1, 10); // [1,2,3,4,5,6,7,8,9,10]
var skipped = numbers.Skip(5); // Skips first 5 elements
var taken = numbers.Take(5); // Takes first 5 elements
Console.WriteLine("Skip(5): " + string.Join(", ", skipped));
Console.WriteLine("Take(5): " + string.Join(", ", taken));
//Output
Skip(5): 6, 7, 8, 9, 10
Take(5): 1, 2, 3, 4, 5
TakeWhile / SkipWhile
Takes elements from the start of a sequence as long as the condition is true. Stops when it becomes false.
Skips elements from the start of a sequence as long as the condition is true, and then takes the rest.
var numbers = new[] { 1, 2, 3, 7, 8, 9 };
var result = numbers.TakeWhile(n => n < 5);
Console.WriteLine(string.Join(", ", result));
//Output
1, 2, 3
var numbers = new[] { 1, 2, 3, 7, 8, 9 };
var result = numbers.SkipWhile(n => n < 5);
Console.WriteLine(string.Join(", ", result));
//Output
7, 8, 9