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()
).