I'm using the Simple Injector as my IoC container. I have developed a number of (excuse me probably using the wrong term) partially closed implementations, for a single generic interface.
I would like to be in a position to request the generic interface, and based on the types supplied, have Simple Injector return the correct class implementation. (I can understand this may be a no-no as implementations could overlap if done wrong etc. but I'd still like to know if it can be done.)
Based on the code snippets below, how can I configure Simple Injector to return me an instance of ITrim<Ford, Green>
?
Common base class layer:
public interface IColour { }
public interface IVehicle { }
public interface ITrim<TVehicle, TColour>
where TVehicle : IVehicle
where TColour : IColour
{
void Trim(TVehicle vehicle, TColour colour);
}
public abstract class TrimVehicle<TVehicle, TColour> : ITrim<TVehicle, TColour>
where TVehicle : IVehicle
where TColour : IColour
{
public virtual void Trim(TVehicle vehicle, TColour colour) { }
}
Middle layer, providing common code for a type of vehicle:
public abstract class Green : IColour { }
public abstract class Blue : IColour { }
public abstract class Car : IVehicle { }
public abstract class Bike : IVehicle { }
public abstract class TrimCar<TCar, TColour> : TrimVehicle<TCar, TColour>
where TCar : Car
where TColour : IColour
{
public override void Trim(TVehicle vehicle, TColour colour)
{
base.Trim(vehicle, colour);
}
}
public abstract class TrimBike<TBike, TColour> : TrimVehicle<TBike, TColour>
where TBike : Bike
where TColour : IColour
{
public override void Trim(TVehicle vehicle, TColour colour)
{
base.Trim(vehicle, colour);
}
}
Final layer, providing more specific implementations:
public class Ford : Car { }
public class TrimFord<TFord, TColour> : TrimCar<TFord, TColour>
where TFord : Ford
where TColour : IColour
{
public override void Trim(TVehicle vehicle, TColour colour)
{
base.Trim(vehicle, colour);
}
}
public class Yamaha : Bike { }
public class TrimYamaha<TYamaha, TColour> : TrimBike<TYamaha, TColour>
where TYamaha : Yamaha
where TColour : IColour
{
public override void Trim(TVehicle vehicle, TColour colour)
{
base.Trim(vehicle, colour);
}
}
TLDR;
container.Register(typeof(ITrim<,>), typeof(ITrim<,>).Assembly);
Long, but outdated answer:
A very complicated question, with a very simple answer:
container.RegisterOpenGeneric(typeof(ITrim<,>), typeof(TrimCar<,>));
container.RegisterOpenGeneric(typeof(ITrim<,>), typeof(TrimFord<,>));
container.RegisterOpenGeneric(typeof(ITrim<,>), typeof(TrimYamaha<,>));
This works, because Simple Injector respects any generic type constraint of a given type (or at least, any it handles any nasty bizarre type constraint that I could think of). So as long as you make sure the registered open generic types (TrimCar
, TrimFord
, and TrimYamaha
) do not overlap, it will work as expected.
If they do overlap, the container will throw an exception telling you that multiple observers of the ResolveUnregisteredType
event tried to register {some type}.
Although you should be careful that the use of these overlapping types does not complicate your application, in general I find the use of generic type constraints very convenient and I use it all the time (especially when registering decorators).
UPDATE
If you have a set of non-generic decorators, the current implementation of RegisterManyForOpenGeneric
can not distinguish them from 'normal' types, and it tries to register them. If you don't want that, you can register your types as follows:
var types = OpenGenericBatchRegistrationExtensions.GetTypesToRegister(
typeof(ITrim<,>), typeof(ITrim<,>).Assembly)
.Where(type => !type.Name.EndsWith("Decorator");
container.RegisterManyForOpenGeneric(typeof(ITrim<,>), types);
The RegisterManyForOpenGeneric
extension methods use the GetTypesToRegister
internally as well.
UPDATE 2
The RegisterManyForOpenGeneric
method of Simple Injector 2 now recognizes non-generic decorators, so with v2 you are able to simply do this:
container.RegisterManyForOpenGeneric(
typeof(ITrim<,>),
typeof(ITrim<,>).Assembly);
Much easier, don't you think?
UPDATE 3
Simple Injector v3 obsoleted RegisterManyForOpenGeneric
and removed them completely from v4. For v3 and up, Register
can be used instead:
container.Register(typeof(ITrim<,>), typeof(ITrim<,>).Assembly);