A delegate type declares an exact signature, but a method with a compatible but not identical signature is often what you have. Before C# 2.0 a delegate could only be bound to a method whose return type and parameter types matched exactly, so a method returning Dog could not be assigned to a delegate returning Animal.
C# 2.0 adds variance to delegate binding. A delegate can reference a method with:
- A less derived return type than the delegate declares - covariance
- More derived parameter types than the delegate declares - contravariance
Code
csharp
delegate Animal AnimalFactory();
delegate void DogHandler(Dog dog);
static Dog CreateDog() => new Dog();
static void HandleAnimal(Animal animal) => animal.Feed();
// Covariance: returns Dog, delegate wants Animal
AnimalFactory factory = CreateDog;
// Contravariance: takes Animal, delegate wants Dog
DogHandler handler = HandleAnimal;csharp
// A wrapper method was needed to bridge the signature gap
AnimalFactory factory = new AnimalFactory(delegate { return CreateDog(); });
DogHandler handler = new DogHandler(delegate (Dog dog) { HandleAnimal(dog); });Notes
- Names line up with generic variance: a delegate's return type is an output position, so it is covariant, and its parameters are input positions, so they are contravariant
- Only reference types participate. A method returning
intcannot bind to a delegate returningobject, because that would require boxing - This applies to method group conversion too, which is why
Func<object>can be assigned a method returningstringwithout a wrapper lambda - Related: generic covariance C# 4.0 and generic contravariance C# 4.0 extend the same rules to generic type arguments, which is a separate mechanism that arrived later
- See anonymous methods C# 2.0 for the delegate syntax used in the before example