Seamless Delegate Type Casting
In Seamless Generic Type Casting, we saw how the in
and out
keywords can make our generic interfaces polite with respect to casting. A List<string>
can be treated as an IEnumerable<object>
because a) all strings are objects and b) IEnumerable
is declared with the out
keyword. Now, we’ll see how these keywords can be applied to delegate types in addition to interface types.
Suppose we have a a basic hierarchy of entity classes:
public abstract class Employee { ... }
public class FieldTechnician : Employee { ... }
public class SalesPerson : Employee { ... }
Also, suppose we find ourselves creating/persisting many instances of these in our integration tests, and we create a delegate type with some helper methods for doing so:
public delegate T EntityCreator<T>();
public static Employee CreateBasicFieldTechnician()
{
return new FieldTechnician { /* Populate basic properties. */ };
}
public static Employee CreateBasicSalesPerson()
{
return new SalesPerson { /* Populate basic properties. */ };
}
public static Employee[] PersistEmployees(params EntityCreator<Employee>[] creators)
{
var employees = creators.Select(creator => creator()).ToArray();
//Persist all the Employee instances here.
return employees;
}
In our integration tests, we may use this to quickly get some sample employees into our database:
var employees = PersistEmployees(CreateBasicFieldTechnician, CreateBasicSalesPerson);
As we write some tests, we realize that we need some of our sample employees to be populated differently:
EntityCreator<SalesPerson> createSpecializedSalesPerson = () => new SalesPerson{ /* Populate some additional properties. */ };
var employees = PersistEmployees(CreateBasicFieldTechnician, CreateBasicSalesPerson, createSpecializedSalesPerson);
Surprise! This won’t even compile. The compiler can’t assign our EntityCreator<SalesPerson>
to the expected type EntityCreator<Employee>
. That’s strange, since we know that any SalesPerson
it ever makes will obviously be an Employee. As we saw with similar situations around interfaces, we need to use the out
keyword when we define the delegate type, promising to the compiler that the T
will only appear in the return value. Armed with this additional promise, the compiler can safely treat all EntityCreator<FieldTechnician>
as EntityCreator<Employee>
. With the following change, the example compiles as expected:
public delegate T EntityCreator<out T>();