LINQ (Language Integrated Query) is a powerful feature in C# that provides a unified syntax for querying different data sources including collections, databases, XML, and more.
Advantages:
- Unified Syntax: Single query syntax works across multiple data sources (objects, databases, XML, etc.)
- Type Safety: Compile-time checking prevents runtime errors
- IntelliSense Support: IDE provides autocomplete and suggestions
- Readable Code: Declarative syntax is more intuitive than traditional loops
- Less Code: Reduces boilerplate code significantly
- Strongly Typed: Leverages C#'s type system for safety
- Extensible: Can create custom LINQ operators
- Deferred Execution: Queries execute only when enumerated, improving performance
// Traditional approach
List evenNumbers = new List();
foreach (int num in numbers)
{
if (num % 2 == 0)
evenNumbers.Add(num);
}
// LINQ approach
var evenNumbers = numbers.Where(n => n % 2 == 0);
Related Resources
Query Syntax (Comprehension Syntax):
- SQL-like syntax using keywords like
from,where,select - More readable for complex queries with multiple operations
- Limited to common operations
Method Syntax (Fluent Syntax):
- Uses extension methods with lambda expressions
- More flexible and supports all LINQ operators
- Better for simple operations and chaining
// Query Syntax
var queryResult = from student in students
where student.Age > 18
orderby student.Name
select student.Name;
// Method Syntax
var methodResult = students
.Where(s => s.Age > 18)
.OrderBy(s => s.Name)
.Select(s => s.Name);
// Both produce identical results
// Query syntax is converted to method syntax by the compiler
Key Differences:
- Query syntax requires
fromandselect/groupclauses - Method syntax can access all LINQ operators
- You can mix both syntaxes in a single query
- Method syntax is more commonly used in practice
Related Resources
These methods retrieve elements from a collection but differ in their expectations and error handling:
First()
- Returns the first element
- Throws
InvalidOperationExceptionif sequence is empty - Use when you expect at least one element
FirstOrDefault()
- Returns the first element or default value (null for reference types, 0 for numbers)
- Never throws exception for empty sequences
- Use when the sequence might be empty
Single()
- Returns the only element
- Throws if sequence is empty OR has more than one element
- Use when you expect exactly one element
SingleOrDefault()
- Returns the only element or default value
- Throws if sequence has more than one element (but not if empty)
- Use when you expect zero or one element
var numbers = new List { 1, 2, 3, 4, 5 };
// First() - returns 1
var first = numbers.First();
// FirstOrDefault() - returns 1, or 0 if empty
var firstOrDefault = numbers.FirstOrDefault();
// Single() - throws exception (more than one element)
var single = numbers.Single(); // InvalidOperationException!
// With predicate
var firstEven = numbers.First(n => n % 2 == 0); // returns 2
var singleFive = numbers.Single(n => n == 5); // returns 5
// Empty sequence
var empty = new List();
// empty.First(); // throws InvalidOperationException
var result = empty.FirstOrDefault(); // returns 0
Related Resources
Deferred Execution means LINQ queries are not executed when they are defined, but only when the results are actually enumerated.
How it works:
- Query definition creates an execution plan
- Actual execution happens when you iterate (foreach, ToList(), Count(), etc.)
- Query is re-executed each time you enumerate it
- Benefits: improved performance, fresh data on each execution
var numbers = new List { 1, 2, 3, 4, 5 };
// Query is DEFINED but NOT executed
var query = numbers.Where(n => n > 2);
Console.WriteLine("Query defined");
// NOW the query executes (deferred execution)
foreach (var num in query)
{
Console.WriteLine(num); // Outputs: 3, 4, 5
}
// Modifying source affects query results
numbers.Add(6);
numbers.Add(7);
// Query executes AGAIN with new data
foreach (var num in query)
{
Console.WriteLine(num); // Outputs: 3, 4, 5, 6, 7
}
// Force immediate execution
var immediateResult = numbers.Where(n => n > 2).ToList();
numbers.Add(8); // This won't affect immediateResult
Operations with Immediate Execution:
ToList(),ToArray(),ToDictionary()Count(),Sum(),Average(),Max(),Min()First(),Single(),Last()Any(),All()
Operations with Deferred Execution:
Where(),Select(),OrderBy()Skip(),Take(),GroupBy()Join(),SelectMany()
Related Resources
Select()
- Projects each element into a new form (1-to-1 transformation)
- Returns
IEnumerable<T> - Maintains the structure of the collection
SelectMany()
- Flattens nested collections into a single sequence (1-to-many transformation)
- Returns flattened
IEnumerable<T> - Useful for hierarchical or nested data
// Sample data
var schools = new List
{
new School
{
Name = "Lincoln High",
Students = new[] { "Alice", "Bob" }
},
new School
{
Name = "Jefferson High",
Students = new[] { "Charlie", "Diana" }
}
};
// Select() - Returns IEnumerable
var selectResult = schools.Select(s => s.Students);
// Result: [ ["Alice", "Bob"], ["Charlie", "Diana"] ]
// You get a collection of collections
// SelectMany() - Returns IEnumerable
var selectManyResult = schools.SelectMany(s => s.Students);
// Result: ["Alice", "Bob", "Charlie", "Diana"]
// You get a flattened single collection
// Another example
var numbers = new List<List>
{
new List { 1, 2, 3 },
new List { 4, 5, 6 },
new List { 7, 8, 9 }
};
// Select - nested structure preserved
var selected = numbers.Select(list => list);
// Type: IEnumerable<List>
// SelectMany - flattened
var flattened = numbers.SelectMany(list => list);
// Type: IEnumerable
// Result: [1, 2, 3, 4, 5, 6, 7, 8, 9]
// Practical example: Get all words from sentences
var sentences = new[] { "Hello world", "LINQ is powerful" };
var allWords = sentences.SelectMany(s => s.Split(' '));
// Result: ["Hello", "world", "LINQ", "is", "powerful"]