Deep Dive: Advanced C# — Generics, Delegates & Events
Advanced C# features every senior developer uses: generics with constraints, delegates and Func/Action, events and the observer pattern, expression trees, and pattern matching.
Deep Dive: Advanced C# — Generics, Delegates & Events
These features separate junior from senior C# developers. Understanding them unlocks framework-level thinking and more expressive, reusable code.
Generics
// Generic class — type-safe without boxing
public class Repository<T> where T : class
{
private readonly List<T> _items = new();
public void Add(T item) => _items.Add(item);
public T? Find(Func<T, bool> predicate) => _items.FirstOrDefault(predicate);
public IReadOnlyList<T> GetAll() => _items.AsReadOnly();
}
// Generic method
public static T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) >= 0 ? a : b;
Console.WriteLine(Max(3, 7)); // 7
Console.WriteLine(Max("apple", "banana")); // bananaGeneric Constraints
// where T : class — T must be a reference type
// where T : struct — T must be a value type (no null)
// where T : new() — T must have a parameterless constructor
// where T : SomeBaseClass — T must inherit from SomeBaseClass
// where T : ISomeInterface — T must implement the interface
// where T : notnull — T cannot be null
public static T CreateAndInitialise<T>() where T : new()
{
return new T();
}
public static void Process<TEntity, TId>(TEntity entity)
where TEntity : Entity<TId>
where TId : IComparable<TId>
{
Console.WriteLine(entity.Id);
}Delegates, Func, and Action
// Delegate — a type-safe function pointer
public delegate int MathOperation(int a, int b);
MathOperation add = (a, b) => a + b;
MathOperation mul = (a, b) => a * b;
Console.WriteLine(add(3, 4)); // 7
Console.WriteLine(mul(3, 4)); // 12
// Func<T, TResult> — built-in delegate (up to 16 inputs, one output)
Func<int, int, int> subtract = (a, b) => a - b;
Func<string, bool> isLong = s => s.Length > 10;
// Action<T> — built-in delegate, no return value
Action<string> print = s => Console.WriteLine(s);
Action<int, int> printSum = (a, b) => Console.WriteLine(a + b);
// Predicate<T> — Func<T, bool> shorthand
Predicate<int> isEven = n => n % 2 == 0;
// Multicast delegate — chain multiple methods
Action log = () => Console.Write("Log1 ");
log += () => Console.Write("Log2 ");
log += () => Console.Write("Log3 ");
log(); // Log1 Log2 Log3Events
public class OrderService
{
// Event — a multicast delegate with restricted access
// External code can only += or -=, not invoke directly
public event EventHandler<OrderCreatedEventArgs>? OrderCreated;
public void CreateOrder(int customerId, decimal amount)
{
// ... create order logic ...
// Raise the event (thread-safe with ?.Invoke)
OrderCreated?.Invoke(this, new OrderCreatedEventArgs
{
CustomerId = customerId,
Amount = amount,
CreatedAt = DateTime.UtcNow,
});
}
}
public class OrderCreatedEventArgs : EventArgs
{
public int CustomerId { get; init; }
public decimal Amount { get; init; }
public DateTime CreatedAt { get; init; }
}
// Subscribing
var service = new OrderService();
service.OrderCreated += (sender, args) =>
Console.WriteLine($"Order created for customer {args.CustomerId}: £{args.Amount}");
service.CreateOrder(42, 99.99m);Pattern Matching (C# 8–11)
object value = 42;
// Type pattern
if (value is int number)
Console.WriteLine($"Integer: {number}");
// Switch expression with patterns
string Describe(object obj) => obj switch
{
int n when n > 0 => "positive int",
int n when n < 0 => "negative int",
int => "zero",
string { Length: 0 } => "empty string",
string s => $"string: {s}",
null => "null",
_ => "something else",
};
// Positional (deconstruct) pattern
var point = (3, -5);
string quadrant = point switch
{
(> 0, > 0) => "Q1",
(< 0, > 0) => "Q2",
(< 0, < 0) => "Q3",
(> 0, < 0) => "Q4",
_ => "on axis",
};
// List pattern (C# 11)
int[] data = { 1, 2, 3, 4 };
bool startsWithOneTwo = data is [1, 2, ..];Expression Trees
using System.Linq.Expressions;
// Expression tree — code as data (not executed, inspected)
Expression<Func<int, bool>> expr = x => x > 5;
// Inspect the tree
var binary = (BinaryExpression)expr.Body;
Console.WriteLine(binary.NodeType); // GreaterThan
// Compile and execute
Func<int, bool> compiled = expr.Compile();
Console.WriteLine(compiled(10)); // true
// LINQ providers (EF Core) use expression trees to translate LINQ to SQL
// Func<T, bool> → executes in memory (C# code)
// Expression<Func<T, bool>> → translated to SQL
IQueryable<Order> orders = dbContext.Orders;
orders.Where(o => o.Amount > 100); // EF Core translates this to SQLCovariance and Contravariance
// Covariance (out) — generic type can be used as its base type
IEnumerable<string> strings = new List<string> { "a", "b" };
IEnumerable<object> objects = strings; // works because IEnumerable<out T>
// Contravariance (in) — generic type can accept derived types
IComparer<object> objComparer = Comparer<object>.Default;
IComparer<string> strComparer = objComparer; // works because IComparer<in T>
// Custom covariant interface
public interface IReader<out T>
{
T Read();
}
// Custom contravariant interface
public interface IWriter<in T>
{
void Write(T value);
}Interview Answer
"Generics provide compile-time type safety without boxing — use constraints (
where T : class,where T : new()) to express requirements on type parameters. Delegates are typed function pointers;Func<T, TResult>andAction<T>are the built-in generic versions. Events are multicast delegates where external code can only subscribe/unsubscribe, not invoke — use?.Invokefor thread-safe raising. Pattern matching (switch expressions, type patterns, positional patterns) replaces verbose if/else chains with exhaustive, readable logic. Expression trees represent code as data — LINQ providers like EF Core use them to translate C# lambdas to SQL. Covariance (out) allows aList<string>to be treated asIEnumerable<object>; contravariance (in) allows the reverse for input positions."
Enjoyed this article?
Explore the Backend Systems learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.