簡體   English   中英

Autofac:使用其類型參數的條件來解析開放式泛型

[英]Autofac: resolve an open generic with conditions on its type parameters

在使用Autofac作為其IoC容器的應用程序中,我有一個帶有兩個類型參數的通用接口:

public interface IMapper<TSource, TTarget>
{
    TTarget GetTarget(TSource source);
}

一個包裝器接口IMapper<TSource, TTarget>根據其輸入參數類型動態選擇適當的IMapper<TSource, TTarget>

public interface IDynamicMapper
{
    T GetTarget<T>(object source);
}

我希望我的IDynamicMapper實現在運行時使用IMapper<TSource, TTarget>查找適當的IMapper<TSource, TTarget>組件,該組件的TSource等於source.GetType()並且TTargetT的派生類型(或T本身):

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))
    }
}

我所有的組件都作為應用程序另一部分中的服務接口注冊到Autofac容器中(使用程序集掃描進行記錄)。


更新1

根據史蒂文(Steven)的相關答案,我使用如下方法更新了界面:

public interface IMapper<in TSource, out TTarget>
{
    TTarget GetTarget(TSource source);
}

我的動態映射器的GetTarget()方法如下所示:

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);
}

但是,當調用Resolve(mapperType)Resolve(typeof(IEnumerable<>).MakeGenericType(mapperType)) ,盡管組件存在於容器的注冊中,但仍未解析該組件,該組件映射到服務IMapper<TSource, TTarget> 第一個調用引發異常,第二個調用返回空的可枚舉。

這應該可以解決問題:

T IDynamicMapper.GetTarget<T>(object source) {

    Type mapperType = typeof(IMapper<,>).MakeGenericType(source.GetType(), typeof(T));

    // Will throw when no registration exists.
    // Note the use of 'dynamic'.
    dynamic mapper = scope.Resolve(mapperType);

    return (T)mapper.GetTarget<T>((dynamic)source);
}

Autofac不支持協變泛型類型( ISomething<out T> )。 在這種情況下,另一個IoC容器(例如Simple Injector)可以解決問題,但是為了使其與Autofac一起使用,我最終使用了另一個接口:

服務:

public interface IMapper<TSource, out TTarget> : IMapperLocator<TSource>
{
    TTarget Extract(TSource source);
}
public interface IMapperLocator<TSource>
{
}
public interface IDynamicMapper
{
    T Extract<T>(object source);
}

執行:

public class DynamicMapper : IDynamicMapper
{
    private readonly ILifetimeScope _scope;

    public DynamicMapper(ILifetimeScope scope)
    {
        this._scope = scope;
    }

    T IDynamicMapper.Extract<T>(object source)
    {
        // Get useful types
        Type sourceType = source.GetType();
        Type targetBaseType = typeof(TTarget);
        Type mapperBaseType = typeof(IMapper<,>).MakeGenericType(sourceType, targetBaseType);
        Type locatorType = typeof(IMapperLocator<>).MakeGenericType(sourceType);
        Type enumType = typeof(IEnumerable<>).MakeGenericType(locatorType);

        // Get all mapper implementations that take a TSource with the
        // same type as the source object
        var mappers = (IEnumerable<object>)this._scope.Resolve(enumType);

        // Among all the mappers with the right TSource, select the one
        // with TTarget assignable to T (throws if there is 0 or more than
        // one mapper, as this would be an implementation error)
        dynamic mapper = mappers.Single(x => mapperBaseType.IsAssignableFrom(x.GetType()));

        // The method must implemented implicitly.
        // A workaround would be to use a wrapper (IMapperWrapper<TSource, out TTarget>)
        // that implements the method implicitly and invokes the mapper's method
        // without using dynamic
        return mapper.Extract((dynamic)source);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM