In an application using Autofac as its IoC container, I have a generic interface with two type parameters:
public interface IMapper<TSource, TTarget>
{
TTarget GetTarget(TSource source);
}
and a wrapper interface to dynamically select the appropriate IMapper<TSource, TTarget>
depending on its input parameter types:
public interface IDynamicMapper
{
T GetTarget<T>(object source);
}
I want my implementation of IDynamicMapper to find at runtime the appropriate IMapper<TSource, TTarget>
component, using Autofac, which has a TSource
equal to source.GetType()
and TTarget
being a derived type of T
(or T
itself):
public class DynamicMapper : IDynamicMapper
{
private readonly ILifetimeScope _scope;
public DynamicMapper(ILifetimeScope scope)
{
this._scope = scope;
}
T IDynamicMapper.GetTarget<T>(object source)
{
Type sourceType = source.GetType();
Type targetBaseType = typeof(T);
//TODO: find an IMapper<TSource, TTarget> implementation where
// 1) Condition on TSource: typeof(TSource) == sourceType
// 2) Condition on TTarget: targetBaseType.IsAssignableFrom(typeof(TTarget))
// Many implementations can potentially match these criterias,
// choose the 1st one
// (this should not happen if the application is designed correctly)
if (mapper == null)
{
throw new ArgumentException(
"Could not find an IMapper<TSource, TTarget> implementation" +
" for the supplied parameters"
);
}
// call mapper.GetTarget(source) and return the result
// (mapper is probably dynamic, but its runtime type implements
// TTarget IMapper<TSource, TTarget>.GetTarget(TSource source))
}
}
All my components are registered to the Autofac container as their service interfaces in another part of the application (using assembly scanning for the record).
UPDATE 1
Based on Steven's pertinent answers I updated my interface as follow to use variance:
public interface IMapper<in TSource, out TTarget>
{
TTarget GetTarget(TSource source);
}
My dynamic mapper's GetTarget()
method looks like this:
T IDynamicMapper.GetTarget<T>(object source)
{
Type sourceType = source.GetType();
Type targetBaseType = typeof(TTarget);
Type mapperType = typeof(IMapper<,>).MakeGenericType(sourceType, targetBaseType);
// This fails with ComponentNotRegisteredException
dynamic mapper = this._scope.Resolve(mapperType);
// This also fails (mapper is null):
// IEnumerable<object> mappers = (IEnumerable<object>)this._scope.Resolve(typeof(IEnumerable<>).MakeGenericType(mapperType));
// dynamic mapper = mappers.FirstOrDefault();
// Invoke method
return mapper.GetTarget((dynamic)source);
}
However, when calling Resolve(mapperType)
or Resolve(typeof(IEnumerable<>).MakeGenericType(mapperType))
, the component is not resolved, although it is present in the container's registrations, mapped to the service IMapper<TSource, TTarget>
. The 1st call throws an exception and the 2nd one returns an empty enumerable.