I have the following interfaces:
public interface IModel
{
ModelTypes ModelType { get; } // ModelTypes is an enum
}
public interface IModelConverter<T>
{
byte[] ToBytes(T model);
}
Also, I have 3 implementations of IModel
: ModelA
,ModelB
,ModelC
, and the following classes:
public class ModelAConverter : IModelConverter<ModelA>
public class ModelBConverter : IModelConverter<ModelB>
public class ModelCConverter : IModelConverter<ModelC>
I want to take an IModel
and convert it with ToBytes
method. Obviously, I don't want the caller to know each and every implementation of the converters, so I created the DelegatingConverter
class:
public class DelegatingConverter : IModelConverter<IModel>
{
private readonly Dictionary<ModelTypes, IModelConverter<IModel>> _modelConverters;
public DelegatingConverter()
{
_modelConverters = new Dictionary<ModelTypes, IModelConverter<IModel>>
{
{ModelTypes.TypeA, new ModelAConverter()}, // Argument type ModelAConverter is not assignable to parameter type IModelConverter<IModel>
{ModelTypes.TypeB, new ModelBConverter()}, // Argument type ModelBConverter is not assignable to parameter type IModelConverter<IModel>
{ModelTypes.TypeC, new ModelCConverter()} // Argument type ModelCConverter is not assignable to parameter type IModelConverter<IModel>
};
}
public byte[] ToBytes(IModel model)
{
// Here is the delegation..
return _modelConverters[model.ModelType].ToBytes(model);
}
}
Everything goes great, until I add some converters to the delegating dictionary, _modelConverters
.
The error is:
Argument type
ModelXConverter
is not assignable to parameter typeIModelConverter<IModel>
And I know the solution here should be using covariance on T
in IModelConverter
, so the decleration should be:
public interface IModelConverter<out T>
When I add it, the following error occurs:
Parameter must be input-safe. Invalid variance: The type parameter 'T' must be contravariantly valid on IModelConverter.ToBytes(T). 'T' is covariant.
Is there any way to make it better? I know I can make every ModelConverter
implementation implement IModelConverter<IModel>
, but then I need an obvious cast in the beginning of each implementation.
And I know the solution here should be using covariance on
T
inIModelConverter
No, not really. That would only be the case if any specific IModelConverter
could be regarded as a more general IModelConverter
- and that's not the case. If a converter only knows how to convert ModelB
to bytes, what would you expect it to do with a ModelC
?
The simplest approach is probably to write something more like this:
// This class is general...
public sealed class DelegatingConverter<T> : IModelConverter<IModel>
where T : IModel
{
private readonly IModelConverter<T> originalConverter;
public DelegatingConverter(IModelConverter<T> originalConverter)
{
this.originalConverter = originalConverter;
}
public byte[] ToBytes(IModel model)
{
return originalConverter.ToBytes((T) model);
}
}
Then:
public sealed class KnownModelConverter : IModelConverter<IModel>
{
private static readonly Dictionary<ModelTypes, IModelConverter<IModel>>
= new Dictionary<ModelTypes, IModelConverter<IModel>>
{
{ ModelTypes.TypeA, new DelegatingConverter<ModelA>(new ModelAConverter()) },
{ ModelTypes.TypeB, new DelegatingConverter<ModelB>(new ModelBConverter()) },
{ ModelTypes.TypeC, new DelegatingConverter<ModelC>(new ModelCConverter()) },
};
public byte[] ToBytes(IModel model)
{
// Here is the delegation..
return _modelConverters[model.ModelType].ToBytes(model);
}
}
Basically the key is the cast within DelegatingConverter<T>
- you need to make sure you only use pass instances of the right type to its ToBytes
method - but assuming model.ModelType
is correct, that should be fine. (And if it's not, an exception is probably the right behaviour.)
See more on this question at Stackoverflow