csharp-linq
Use when lINQ (Language Integrated Query) with query and method syntax, deferred execution, expression trees, and performance optimization.
$ インストール
git clone https://github.com/TheBushidoCollective/han /tmp/han && cp -r /tmp/han/jutsu/jutsu-csharp/csharp-linq ~/.claude/skills/han// tip: Run this command in your terminal to install the skill
name: csharp-linq 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);
// Basic query
public IEnumerable<Person> BasicQuery(List<Person> people)
{
var query = from p in people
where p.Age >= 18
select p;
return query;
}
// Query with multiple conditions
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;
}
// Projection with select
public IEnumerable<string> ProjectNames(List<Person> people)
{
var query = from p in people
where p.Age >= 21
select p.Name;
return query;
}
// Anonymous types
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;
}
// Grouping
public IEnumerable<IGrouping<string, Person>> GroupByCity(
List<Person> people)
{
var query = from p in people
group p by p.City;
return query;
}
// Group with projection
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;
}
// Join
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;
}
// Left join
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);
// Filtering
public IEnumerable<Product> FilterProducts(List<Product> products)
{
return products
.Where(p => p.Price > 100)
.Where(p => p.Category == "Electronics");
}
// Ordering
public IEnumerable<Product> OrderProducts(List<Product> products)
{
return products
.OrderBy(p => p.Category)
.ThenByDescending(p => p.Price);
}
// Projection
public IEnumerable<string> ProjectNames(List<Product> products)
{
return products
.Select(p => p.Name.ToUpper());
}
// SelectMany (flatten)
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);
}
// Grouping
public IEnumerable<IGrouping<string, Product>> GroupByCategory(
List<Product> products)
{
return products.GroupBy(p => p.Category);
}
// Aggregation
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);
}
// Any and All
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");
}
// Take and Skip
public IEnumerable<Product> Pagination(
List<Product> products,
int page,
int pageSize)
{
return products
.OrderBy(p => p.Name)
.Skip((page - 1) * pageSize)
.Take(pageSize);
}
// Distinct
public IEnumerable<string> UniqueCategories(List<Product> products)
{
return products
.Select(p => p.Category)
.Distinct();
}
// Set operations
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
{
// Deferred execution demonstration
public void DeferredExecutionDemo()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Query defined but not executed
var query = numbers.Where(n => n > 2);
Console.WriteLine("Before modification:");
foreach (var n in query) // Executes here
{
Console.WriteLine(n); // 3, 4, 5
}
// Modify source
numbers.Add(6);
numbers.Add(7);
Console.WriteLine("After modification:");
foreach (var n in query) // Executes again with new data
{
Console.WriteLine(n); // 3, 4, 5, 6, 7
}
}
// Immediate execution with ToList
public void ImmediateExecution()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Execute immediately and cache results
var list = numbers.Where(n => n > 2).ToList();
numbers.Add(6);
numbers.Add(7);
// list still contains only 3, 4, 5
foreach (var n in list)
{
Console.WriteLine(n);
}
}
// Operators that force immediate execution
public void ImmediateExecutionOperators()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// These execute immediately
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);
// Aggregates execute immediately
int count = numbers.Count(n => n > 2);
int sum = numbers.Sum();
int max = numbers.Max();
bool any = numbers.Any(n => n > 10);
}
// Multiple enumeration issue
public void MultipleEnumeration()
{
var numbers = GetNumbers(); // IEnumerable
// Bad: enumerates twice
int count = numbers.Count();
int sum = numbers.Sum();
// Good: enumerate once, cache
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);
// Complex filtering and projection
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);
}
// Nested queries
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)
});
}
// Window functions
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)
});
}
// Hierarchical data
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
{
// Use Count property instead of Count() method
public void CountOptimization(List<int> numbers)
{
// Slow: iterates entire collection
int count1 = numbers.Where(n => n > 0).Count();
// Fast: uses Count property if available
int count2 = numbers.Count;
// Use Any() instead of Count() for existence check
bool hasItems = numbers.Any(); // Fast
bool hasItems2 = numbers.Count() > 0; // Slow
}
// Avoid multiple enumerations
public void AvoidMultipleEnumerations()
{
var query = GetExpensiveQuery();
// Bad: enumerates multiple times
int count = query.Count();
int sum = query.Sum();
var first = query.First();
// Good: enumerate once
var list = query.ToList();
count = list.Count;
sum = list.Sum();
first = list.First();
}
// Use Where before Select
public IEnumerable<string> FilterBeforeProject(List<int> numbers)
{
// Good: filter first (fewer items to project)
return numbers
.Where(n => n > 100)
.Select(n => n.ToString());
// Bad: project all, then filter
// return numbers
// .Select(n => n.ToString())
// .Where(s => int.Parse(s) > 100);
}
// Use FirstOrDefault instead of Where().First()
public int? FindFirst(List<int> numbers)
{
// Good: stops at first match
return numbers.FirstOrDefault(n => n > 100);
// Bad: filters all, then takes first
// return numbers.Where(n => n > 100).FirstOrDefault();
}
// Avoid unnecessary sorting
public IEnumerable<int> TakeWithoutSort(List<int> numbers)
{
// If you only need top N, consider partial sort
return numbers
.OrderByDescending(n => n)
.Take(10);
// Better for large collections: use PriorityQueue or similar
}
// Use AsParallel for CPU-intensive operations
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);
// LINQ to Objects (in-memory)
public void LinqToObjects(List<Customer> customers)
{
// Executes in memory
var query = customers
.Where(c => c.City == "Seattle")
.OrderBy(c => c.Name)
.Select(c => new { c.Name, c.City });
// Can use any C# method
var withMethods = customers
.Where(c => IsValidCity(c.City))
.ToList();
}
// LINQ to SQL (database)
public void LinqToSQL()
{
// In real application, this would be DbContext
// var query = dbContext.Customers
// .Where(c => c.City == "Seattle") // Translates to SQL
// .OrderBy(c => c.Name)
// .Select(c => new { c.Name, c.City });
// Can't use arbitrary C# methods in SQL query
// This would throw runtime error:
// .Where(c => IsValidCity(c.City))
// Use AsEnumerable() to switch to LINQ to Objects
// var mixed = dbContext.Customers
// .Where(c => c.City == "Seattle") // SQL
// .AsEnumerable()
// .Where(c => IsValidCity(c.City)); // In-memory
}
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
{
// Building expression trees
public void BuildExpressionTree()
{
// Manual expression tree
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);
// Compile and execute
Func<int, int> func = expr.Compile();
int result = func(10); // 15
}
// From lambda to expression
public void LambdaToExpression()
{
// Expression tree from lambda
Expression<Func<int, bool>> expr = x => x > 5;
// Compile to delegate
Func<int, bool> func = expr.Compile();
bool result = func(10); // true
}
// Analyzing expressions
public void AnalyzeExpression()
{
Expression<Func<int, bool>> expr = x => x > 5;
// Get parts
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}");
}
// Dynamic query building
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()orToArray()when you need to enumerate multiple times - Use
Any()instead ofCount() > 0for existence checks - Filter with
Where()before projecting withSelect() - Use
FirstOrDefault()instead ofWhere().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
IEnumerablecausing performance issues - Using LINQ on database queries without understanding SQL translation
- Calling
ToList()too early, losing deferred execution benefits - Using
Count()method instead ofCountproperty on collections - Not disposing
IEnumerablefrom 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()whereSelectMany()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
Repository
