Lesson 10 of 13
Generics, Interfaces & Delegates
Learn for reusable typed code, for defining contracts, and delegates for passing around as values.
using System;
using System.Collections.Generic;
// A generic class with a constraint
public class Repository<T> where T : class, IEntity
{
private readonly Dictionary<int, T> _items = new();
public void Add(T item) => _items[item.Id] = item;
public T? Get(int id) => _items.TryGetValue(id, out var v) ? v : null;
public IEnumerable<T> All() => _items.Values;
public bool Remove(int id) => _items.Remove(id);
}
public interface IEntity { int Id { get; } }
public record Product(int Id, string Name, decimal Price) : IEntity;
var repo = new Repository<Product>();
repo.Add(new Product(1, "Keyboard", 79.99m));
Console.WriteLine(repo.Get(1)?.Name); // Keyboard
// Common constraints:
// where T : class reference type
// where T : struct value type
// where T : new() has a parameterless constructor
// where T : IComparable<T> implements an interface
// where T : Base derives from a class
public static T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) >= 0 ? a : b;// Interfaces: contracts, and the seam that makes code testable.
public interface IClock { DateTime Now { get; } }
public interface INotifier { Task SendAsync(string to, string message); }
public class SystemClock : IClock { public DateTime Now => DateTime.UtcNow; }
public class FakeClock : IClock { public DateTime Now { get; set; } } // for tests
public class ReminderService
{
private readonly IClock _clock;
private readonly INotifier _notifier;
// Dependencies come in through the constructor — this is dependency injection.
public ReminderService(IClock clock, INotifier notifier)
{
_clock = clock;
_notifier = notifier;
}
public async Task RunAsync(IEnumerable<(string Email, DateTime Due)> tasks)
{
foreach (var (email, due) in tasks)
if (due <= _clock.Now)
await _notifier.SendAsync(email, "Your task is due");
}
}
// Explicit implementation when two interfaces collide
public class Duck : IWalker, ISwimmer
{
void IWalker.Move() => Console.WriteLine("waddle");
void ISwimmer.Move() => Console.WriteLine("paddle");
}
public interface IWalker { void Move(); }
public interface ISwimmer { void Move(); }// Delegates: a type-safe reference to a method.
public delegate decimal PriceRule(decimal price);
PriceRule tax = p => p * 1.2m;
PriceRule discount = p => p - 5m;
// Built-in generic delegates — you rarely need to declare your own:
// Func<T, TResult> returns a value
// Action<T> returns void
// Predicate<T> returns bool
Func<int, int, int> add = (a, b) => a + b;
Action<string> log = msg => Console.WriteLine($"[log] {msg}");
Predicate<int> isEven = n => n % 2 == 0;
// Delegates combine (multicast)
PriceRule both = p => discount(tax(p));
Console.WriteLine(both(100m)); // 115.0
// Events: a delegate the outside world may subscribe to but not raise
public class Downloader
{
public event EventHandler<int>? ProgressChanged;
public void Download()
{
for (int i = 0; i <= 100; i += 25)
ProgressChanged?.Invoke(this, i); // ?. avoids "no subscribers" crash
}
}
var d = new Downloader();
d.ProgressChanged += (sender, pct) => Console.WriteLine($"{pct}%");
d.Download();Finished reading?Mark it done to track your progress.
Tip: use the ← and → keys to move between lessons.