Lesson 10 of 13

Generics, Interfaces & Delegates

Learn for reusable typed code, for defining contracts, and delegates for passing around as values.

C#
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;
C#
// 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(); }
C#
// 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.