Lab: Variant Generic Delegates#
Objective#
In this lab exercise, we will delve deep into the concept of variant generic delegates in C#. Our goal is to understand and implement both covariance and contravariance in generic delegates. By the end of this lab, we should have a firm grasp of how to use the in
and out
keywords effectively with generic delegates to achieve type safety while maintaining flexibility.
Provided code#
Carefully review the provided code. Notice the class hierarchy and the initial delegates definitions.
class Fruit
{
public string Color { get; set; }
}
class Apple : Fruit { }
delegate T Factory<T>();
delegate void Consumer<T>(T item);
Instructions#
Step 1: Implement Covariance#
Begin by defining a method that returns an
Apple
object.Assign this method to a
Factory<Apple>
delegate variable.Try to assign the
Factory<Apple>
variable to aFactory<Fruit>
variable. Does it work?Update the
Factory<T>
delegate to support covariance by adding theout
keyword.Now, assign the
Factory<Apple>
variable to aFactory<Fruit>
variable.Print the color of the fruit produced by the
Factory<Fruit>
delegate.
Step 2: Implement Contravariance#
Define a method that consumes a
Fruit
object, perhaps by simply printing its color.Assign this method to a
Consumer<Fruit>
delegate variable.Try to assign the
Consumer<Fruit>
delegate to aConsumer<Apple>
variable. Does it work?Update the
Consumer<T>
delegate to support contravariance using thein
keyword.Now, assign the
Consumer<Fruit>
delegate to aConsumer<Apple>
variable.Pass an
Apple
object with a specific variety to theConsumer<Apple>
delegate and see how it’s consumed.
🤔 Reflection
Think about the benefits of having this kind of flexibility with generic delegates. Why might we want to assign a delegate of one type to a delegate of another type? In what real-world scenarios might this be useful?
Challenge#
Challenge 1: Variant generic delegates and LINQ#
As mentioned in the study material, built-in generic delegates such as Func
, Action
, and Predicate
have variant parameters. Let’s explore this:
Create a list of
Apple
objects.Define a
Func<Fruit, string>
delegate that takes aFruit
and returns its color.Use this delegate with the
Select
LINQ method on the list of apples to retrieve a list of apple colors. Notice how we can pass a delegate that expects aFruit
to a method that works withApple
.
🤔 Reflection
Having gone through the challenge, can you think of other places in the .NET framework where such variance can be beneficial? How does this improve code reuse and adaptability?
Challenge 2: Multiple Levels of Hierarchy#
Consider we have another class GrannySmith
that inherits from Apple
. Can you showcase variance (both covariance and contravariance) using three levels of hierarchy: Fruit
, Apple
, and GrannySmith
? Think about how you might define and use delegates in this context.
Conclusion#
We hope this lab exercise deepens your understanding of variant generic delegates in C#.
Happy coding! 🤓