| name | csharp-linq |
| user-invocable | false |
| description | Use when lINQ (Language Integrated Query) with query and method syntax, deferred execution, expression trees, and performance optimization. |
| allowed-tools | ["Read","Write","Edit","Grep","Glob","Bash"] |
C# LINQ
LINQ (Language Integrated Query) provides a consistent query experience across
different data sources including collections, databases, XML, and more. It
combines the power of SQL-like queries with C# type safety and IntelliSense
support, enabling expressive and maintainable data manipulation code.
Query Syntax
Query syntax provides SQL-like syntax for querying data sources, compiled to
method calls at compile time.
using System;
using System.Collections.Generic;
using System.Linq;
public class QuerySyntaxExamples
{
public record Person(string Name, int Age, string City);
public IEnumerable<Person> BasicQuery(List<Person> people)
{
var query = from p in people
where p.Age >= 18
select p;
return query;
}
public IEnumerable<Person> MultipleConditions(List<Person> people)
{
var query = from p in people
where p.Age >= 18 && p.City == "Seattle"
orderby p.Name
select p;
return query;
}
public IEnumerable<string> ProjectNames(List<Person> people)
{
var query = from p in people
where p.Age >= 21
select p.Name;
return query;
}
public IEnumerable<object> AnonymousProjection(List<Person> people)
{
var query = from p in people
select new
{
p.Name,
p.Age,
IsAdult = p.Age >= 18
};
return query;
}
public IEnumerable<IGrouping<string, Person>> GroupByCity(
List<Person> people)
{
var query = from p in people
group p by p.City;
return query;
}
public IEnumerable<object> GroupWithProjection(List<Person> people)
{
var query = from p in people
group p by p.City into cityGroup
select new
{
City = cityGroup.Key,
Count = cityGroup.Count(),
AverageAge = cityGroup.Average(p => p.Age)
};
return query;
}
public record Order(int Id, string PersonName, decimal Amount);
public IEnumerable<object> JoinExample(
List<Person> people,
List<Order> orders)
{
var query = from p in people
join o in orders on p.Name equals o.PersonName
select new
{
p.Name,
p.Age,
OrderAmount = o.Amount
};
return query;
}
public IEnumerable<object> LeftJoin(
List<Person> people,
List<Order> orders)
{
var query = from p in people
join o in orders on p.Name equals o.PersonName
into personOrders
from po in personOrders.DefaultIfEmpty()
select new
{
p.Name,
OrderAmount = po?.Amount ?? 0
};
return query;
}
}
Method Syntax
Method syntax uses extension methods for querying, providing more flexibility
and access to all LINQ operators.
using System;
using System.Collections.Generic;
using System.Linq;
public class MethodSyntaxExamples
{
public record Product(string Name, decimal Price, string Category);
public IEnumerable<Product> FilterProducts(List<Product> products)
{
return products
.Where(p => p.Price > 100)
.Where(p => p.Category == "Electronics");
}
public IEnumerable<Product> OrderProducts(List<Product> products)
{
return products
.OrderBy(p => p.Category)
.ThenByDescending(p => p.Price);
}
public IEnumerable<string> ProjectNames(List<Product> products)
{
return products
.Select(p => p.Name.ToUpper());
}
public IEnumerable<int> FlattenLists()
{
var lists = new List<List<int>>
{
new List<int> { 1, 2, 3 },
new List<int> { 4, 5 },
new List<int> { 6, 7, 8, 9 }
};
return lists.SelectMany(list => list);
}
public IEnumerable<IGrouping<string, Product>> GroupByCategory(
List<Product> products)
{
return products.GroupBy(p => p.Category);
}
public void AggregationExamples(List<Product> products)
{
decimal total = products.Sum(p => p.Price);
decimal average = products.Average(p => p.Price);
decimal max = products.Max(p => p.Price);
decimal min = products.Min(p => p.Price);
int count = products.Count();
int expensiveCount = products.Count(p => p.Price > 500);
}
public void ExistenceChecks(List<Product> products)
{
bool hasExpensive = products.Any(p => p.Price > 1000);
bool allAffordable = products.All(p => p.Price < 100);
bool hasElectronics = products.Any(p =>
p.Category == "Electronics");
}
public IEnumerable<Product> Pagination(
List<Product> products,
int page,
int pageSize)
{
return products
.OrderBy(p => p.Name)
.Skip((page - 1) * pageSize)
.Take(pageSize);
}
public IEnumerable<string> UniqueCategories(List<Product> products)
{
return products
.Select(p => p.Category)
.Distinct();
}
public void SetOperations(
List<Product> products1,
List<Product> products2)
{
var union = products1.Union(products2);
var intersect = products1.Intersect(products2);
var except = products1.Except(products2);
}
}
Deferred Execution
LINQ queries use deferred execution, meaning the query executes when
enumerated, not when defined.
using System;
using System.Collections.Generic;
using System.Linq;
public class DeferredExecutionExamples
{
public void DeferredExecutionDemo()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var query = numbers.Where(n => n > 2);
Console.WriteLine("Before modification:");
foreach (var n in query)
{
Console.WriteLine(n);
}
numbers.Add(6);
numbers.Add(7);
Console.WriteLine("After modification:");
foreach (var n in query)
{
Console.WriteLine(n);
}
}
public void ImmediateExecution()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var list = numbers.Where(n => n > 2).ToList();
numbers.Add(6);
numbers.Add(7);
foreach (var n in list)
{
Console.WriteLine(n);
}
}
public void ImmediateExecutionOperators()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var array = numbers.Where(n => n > 2).ToArray();
var dict = numbers.ToDictionary(n => n, n => n * 2);
var hashSet = numbers.ToHashSet();
var lookup = numbers.ToLookup(n => n % 2);
int count = numbers.Count(n => n > 2);
int sum = numbers.Sum();
int max = numbers.Max();
bool any = numbers.Any(n => n > 10);
}
public void MultipleEnumeration()
{
var numbers = GetNumbers();
int count = numbers.Count();
int sum = numbers.Sum();
var list = numbers.ToList();
count = list.Count;
sum = list.Sum();
}
private IEnumerable<int> GetNumbers()
{
Console.WriteLine("Generating numbers...");
for (int i = 1; i <= 5; i++)
{
yield return i;
}
}
}
Complex Queries
Combining multiple LINQ operations for complex data transformations.
using System;
using System.Collections.Generic;
using System.Linq;
public class ComplexQueries
{
public record Student(string Name, int Grade, string Subject,
int Score);
public record Course(string Subject, string Teacher, int Credits);
public IEnumerable<object> StudentReport(
List<Student> students,
List<Course> courses)
{
return students
.Where(s => s.Grade >= 10)
.GroupBy(s => new { s.Name, s.Grade })
.Select(g => new
{
g.Key.Name,
g.Key.Grade,
Subjects = g.Select(s => s.Subject).Distinct(),
AverageScore = g.Average(s => s.Score),
TotalCredits = g.Join(
courses,
s => s.Subject,
c => c.Subject,
(s, c) => c.Credits
).Sum()
})
.OrderByDescending(s => s.AverageScore);
}
public IEnumerable<object> TopStudentsBySubject(
List<Student> students)
{
return students
.GroupBy(s => s.Subject)
.Select(g => new
{
Subject = g.Key,
TopStudent = g
.OrderByDescending(s => s.Score)
.Select(s => new { s.Name, s.Score })
.FirstOrDefault(),
ClassAverage = g.Average(s => s.Score)
});
}
public IEnumerable<object> RunningTotal(List<Student> students)
{
return students
.OrderBy(s => s.Name)
.ThenBy(s => s.Subject)
.Select((s, index) => new
{
s.Name,
s.Subject,
s.Score,
RunningTotal = students
.Take(index + 1)
.Sum(x => x.Score)
});
}
public record Category(string Name, string Parent);
public IEnumerable<object> BuildHierarchy(List<Category> categories)
{
return categories
.Where(c => c.Parent == null)
.Select(parent => new
{
parent.Name,
Children = categories
.Where(c => c.Parent == parent.Name)
.Select(child => new
{
child.Name,
Grandchildren = categories
.Where(gc => gc.Parent == child.Name)
})
});
}
}
Performance Optimization
Understanding LINQ performance characteristics and optimization techniques.
using System;
using System.Collections.Generic;
using System.Linq;
public class PerformanceOptimization
{
public void CountOptimization(List<int> numbers)
{
int count1 = numbers.Where(n => n > 0).Count();
int count2 = numbers.Count;
bool hasItems = numbers.Any();
bool hasItems2 = numbers.Count() > 0;
}
public void AvoidMultipleEnumerations()
{
var query = GetExpensiveQuery();
int count = query.Count();
int sum = query.Sum();
var first = query.First();
var list = query.ToList();
count = list.Count;
sum = list.Sum();
first = list.First();
}
public IEnumerable<string> FilterBeforeProject(List<int> numbers)
{
return numbers
.Where(n => n > 100)
.Select(n => n.ToString());
}
public int? FindFirst(List<int> numbers)
{
return numbers.FirstOrDefault(n => n > 100);
}
public IEnumerable<int> TakeWithoutSort(List<int> numbers)
{
return numbers
.OrderByDescending(n => n)
.Take(10);
}
public IEnumerable<int> ParallelQuery(List<int> numbers)
{
return numbers
.AsParallel()
.Where(n => ExpensiveOperation(n))
.Select(n => n * 2);
}
private IEnumerable<int> GetExpensiveQuery()
{
return Enumerable.Range(1, 1000)
.Where(n => n % 2 == 0);
}
private bool ExpensiveOperation(int n)
{
System.Threading.Thread.Sleep(1);
return n > 50;
}
}
LINQ to Objects vs LINQ to SQL
Understanding differences between in-memory and database queries.
using System;
using System.Collections.Generic;
using System.Linq;
public class LinqProviders
{
public record Customer(int Id, string Name, string City);
public void LinqToObjects(List<Customer> customers)
{
var query = customers
.Where(c => c.City == "Seattle")
.OrderBy(c => c.Name)
.Select(c => new { c.Name, c.City });
var withMethods = customers
.Where(c => IsValidCity(c.City))
.ToList();
}
public void LinqToSQL()
{
}
private bool IsValidCity(string city)
{
return !string.IsNullOrEmpty(city) && city.Length > 2;
}
}
Expression Trees
Understanding expression trees for advanced LINQ scenarios.
using System;
using System.Linq.Expressions;
public class ExpressionTreeExamples
{
public void BuildExpressionTree()
{
ParameterExpression param = Expression.Parameter(typeof(int), "x");
BinaryExpression body = Expression.Add(
param,
Expression.Constant(5)
);
Expression<Func<int, int>> expr =
Expression.Lambda<Func<int, int>>(body, param);
Func<int, int> func = expr.Compile();
int result = func(10);
}
public void LambdaToExpression()
{
Expression<Func<int, bool>> expr = x => x > 5;
Func<int, bool> func = expr.Compile();
bool result = func(10);
}
public void AnalyzeExpression()
{
Expression<Func<int, bool>> expr = x => x > 5;
var lambda = (LambdaExpression)expr;
var body = (BinaryExpression)lambda.Body;
var left = (ParameterExpression)body.Left;
var right = (ConstantExpression)body.Right;
Console.WriteLine($"Parameter: {left.Name}");
Console.WriteLine($"Operator: {body.NodeType}");
Console.WriteLine($"Constant: {right.Value}");
}
public Expression<Func<T, bool>> BuildPredicate<T>(
string propertyName,
object value)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
MemberExpression property = Expression.Property(param,
propertyName);
ConstantExpression constant = Expression.Constant(value);
BinaryExpression equal = Expression.Equal(property, constant);
return Expression.Lambda<Func<T, bool>>(equal, param);
}
}
Best Practices
- Use method syntax for complex queries with multiple operations
- Use query syntax for queries that look more like SQL
- Call
ToList() or ToArray() when you need to enumerate multiple times
- Use
Any() instead of Count() > 0 for existence checks
- Filter with
Where() before projecting with Select()
- Use
FirstOrDefault() instead of Where().First() when finding single item
- Avoid
Select() when you don't need to transform the data
- Use
AsParallel() only for CPU-intensive operations on large datasets
- Be aware of deferred execution and when queries actually execute
- Consider expression tree compilation cost for frequently-used queries
Common Pitfalls
- Multiple enumeration of
IEnumerable causing performance issues
- Using LINQ on database queries without understanding SQL translation
- Calling
ToList() too early, losing deferred execution benefits
- Using
Count() method instead of Count property on collections
- Not disposing
IEnumerable from database queries, leaking connections
- Using LINQ for simple loops where foreach would be clearer
- Excessive
AsParallel() causing overhead instead of speedup
- Capturing variables in lambda expressions causing unintended closures
- Using
Select() where SelectMany() is needed for flattening
- Not understanding operator precedence in complex query expressions
When to Use LINQ
Use LINQ when you need:
- Querying collections, databases, XML, or other data sources uniformly
- Expressive data transformation and filtering operations
- Type-safe queries with compile-time checking and IntelliSense
- Functional programming patterns for data manipulation
- Complex grouping, joining, and aggregation operations
- Declarative code that expresses intent clearly
- Integration with Entity Framework or other ORMs
- Composition of queries from reusable components
- Parallel processing of large datasets with PLINQ
- Consistent API across different data sources
Resources