I know how to force a type parameter to be a subtype of another type:
public interface IMapping<T2>
{
public void Serialize<T3>(T3 obj)
where T3 : T2;
}
...
var mapping = MapManager.Find<Truck>();
mapping.Serialize(new TonkaTruck());
Is there a way to force a type parameter to be a supertype of another type?
public interface IMapping<T2>
{
public void IncludeMappingOf<T1>()
where T2 : T1; // <== doesn't work
}
...
var mapping = MapManager.Find<Truck>();
// Truck inherits Vehicle
// Would like compiler safety here:
mapping.IncludeMappingOf<Vehicle>();
mapping.Serialize(new TonkaTruck());
Currently, I'm having to compare T1 and T2 at runtime using IsSubclassOf
inside IncludeMappingOf
. A compile-safe solution would be preferable. Any ideas?
EDIT: Changed the example to be less design-smelly.
NOTE: The linked question is quite similar, but no suitable answer is given. Hopefully this question will shed some light on that one as well.
EDIT #2:
Simpler example:
public class Holder<T2>
{
public T2 Data { get; set; }
public void AddDataTo<T1>(ICollection<T1> coll)
//where T2 : T1 // <== doesn't work
{
coll.Add(Data); // error
}
}
...
var holder = new Holder<Truck> { Data = new TonkaTruck() };
var list = new List<Vehicle>();
holder.AddDataTo(list);
Compiler: Argument type 'T2' is not assignable to parameter type 'T1'. Yes I know that, I'm trying to get the compiler to allow only cases where T2 IS assignable to parameter type T1!
You can use extension methods to come close to what you want. Using your holder example it would be:
public class Holder<T2>
{
public T2 Data { get; set; }
}
public static class HolderExtensions
{
public static void AddDataTo<T2, T1>(this Holder<T2> holder, ICollection<T1> coll)
where T2 : T1
{
coll.Add(holder.Data);
}
}
That then allows your example calling code to compile without error:
var holder = new Holder<Truck> { Data = new TonkaTruck() };
var list = new List<Vehicle>();
holder.AddDataTo(list);
The mapping example is complicated by the fact that it is an interface. It may be necessary to add an implementation method to the interface if there is no way to implement the extension method from the existing interface. That means you will still need a runtime check, but callers can get the good syntax and compile time checking. That would be something like:
public interface IMapping<T2>
{
void IncludeMappingOf(Type type);
}
public static class MappingExtensions
{
public static void IncludeMappingOf<T2, T1>(this IMapping<T2> mapping)
where T2 : T1
{
mapping.IncludeMappingOf(typeof(T1));
}
}
Unfortunatly, the IncludeMappingOf
does not have a parameter of type T1
so the type parameters cannot be inferred. You are forced to specify both types when calling it:
var mapping = MapManager.Find<Truck>();
mapping.IncludeMappingOf<Truck, Vehicle>();
mapping.Serialize(new TonkaTruck());
That can often be worked around by changing the API to include a parameter (i.e. truckMapping.IncludeMappingOf(vehicleMapping)
), changing which method/class the parameter is on or in fluent APIs creating chains (i.e. mapping.Of<Vehicle>().Include()
).