是否可以注册通用接口而无需在Unity中指定泛型类型?

时间:2017-07-21 21:30:46

标签: c# generics unity-container

假设我有以下内容:

 public interface IDataTranslator<TFrom, TTo>  {
        TTo Translate(TFrom fromObj);
    }

  public class IdentityDataTranslator<T> : IDataTranslator<T, T> {
        public T Translate(T fromObj) {
            return fromObj;
        }
    }

我正在寻找一种方法:

   IdentityDataTranslator<A> trA = UnityContainer.Resolve<IDataTranslator<A, A>>()
   IdentityDataTranslator<B> trB = UnityContainer.Resolve<IDataTranslator<B, B>>()
   IdentityDataTranslator<C> trc = UnityContainer.Resolve<IDataTranslator<C, C>>()

对于所有这些结果(假设我不知道将要解决的泛型的类型)我希望Unity返回具有相应泛型的IdentityDataTranslator的实例。有没有办法以某种方式注册Unity以实现这一目标?

3 个答案:

答案 0 :(得分:1)

您可以通过创建一个继承具有两个通用参数的原始接口的新接口来匹配接口和类之间的泛型参数的数量。假设已知这两个参数始终相同。

[HttpPost]
public bool GetBuildingsById(int[] ids)
{
    var lookAtIds = ids;     // {int[0]}
    return true;
}

答案 1 :(得分:1)

如果我理解您的问题,您可以使用InjectionFactory来设置解决当前类型的方法。它看起来像这样:

    // It's a test implementation of IDataTranslator<From, To>
    public class DataTranslator<TFrom, TTo> : IDataTranslator<TFrom, TTo>
    {
        TTo IDataTranslator<TFrom, TTo>.Translate(TFrom fromObj)
        {
            throw new NotImplementedException();
        }
    }

    ...
    var strResolve = "notSameResolve";
    container.RegisterType(typeof(IDataTranslator<,>), typeof(DataTranslator<,>), strResolve);
    container.RegisterType(typeof(IDataTranslator<,>),
        new InjectionFactory(
            (con, type, str) =>
            {
                var argumets = type.GetGenericArguments();
                if (argumets[0] != argumets[1])
                {
                    return con.Resolve(type, strResolve);
                }

                return con.Resolve(typeof(IdentityDataTranslator<>).MakeGenericType(type.GetGenericArguments()[0]));
            }));

    var trA = (IdentityDataTranslator<A>)container.Resolve<IDataTranslator<A, A>>();
    var trAData = (DataTranslator<A, B>)container.Resolve<IDataTranslator<A, B>>();

因此,如果您尝试使用上面的IDataTranslator<A, B>来解决InjectionFactory,那么当您尝试使用DataTranslator<A, B>解决IdentityDataTranslator<A>时,您会获得IDataTranslator<A, A>并获得 $scriptPath = $MyInvocation.MyCommand.Path $dir = Split-Path $scriptPath $dllPath = $dir + "\MyCacheTools.dll" [Reflection.Assembly]::LoadFrom($dllPath) $files = [MyCacheTools.CacheTools]::DirSearch($cachePath)) ForEach ($file in $files) { Write-Output $file } 相同的论点。

答案 2 :(得分:1)

这是一个与您的问题略有不同的方法。我们不考虑完全注重注册,而是考虑如何解析为正确的类型。

我们的想法是注册将IDataTranslator<TFrom, TTo>映射到DataTranslator<TFrom, TTo>的典型案例。下一步是创建一个Unity容器扩展,以映射特殊情况,其中TFrom与TTo的类型相同,同时解析IDataTranslator<TFrom, TTo>

假设:

public class A { }

public class B { }

public interface IDataTranslator<TFrom, TTo>
{
    TTo Translate(TFrom fromObj);
}

public class DataTranslator<TFrom, TTo> : IDataTranslator<TFrom, TTo>
{
    public TTo Translate(TFrom fromObj)
    {
        return Activator.CreateInstance<TTo>();
    }
}

public class IdentityDataTranslator<T> : IDataTranslator<T, T>
{
    public T Translate(T fromObj)
    {
        return fromObj;
    }
}

接下来创建一个容器扩展来处理IdentityDataTranslator:

public class IdentityGenericsExtension : UnityContainerExtension
{
    private readonly Type identityGenericType;
    private readonly Type baseType;

    public IdentityGenericsExtension(Type identityGenericType, Type baseType)
    {
        // Verify that Types are open generics with the correct number of arguments
        // and that they are compatible (IsAssignableFrom).
        this.identityGenericType = identityGenericType;
        this.baseType = baseType;
    }

    protected override void Initialize()
    {
        this.Context.Strategies.Add(
            new IdentityGenericsBuildUpStrategy(this.identityGenericType, this.baseType),
                UnityBuildStage.TypeMapping);
    }

    private class IdentityGenericsBuildUpStrategy : BuilderStrategy
    {
        private readonly Type identityGenericType;
        private readonly Type baseType;

        public IdentityGenericsBuildUpStrategy(Type identityGenericType, Type baseType)
        {
            this.identityGenericType = identityGenericType;
            this.baseType = baseType;
        }

        public override void PreBuildUp(IBuilderContext context)
        {
            if (context.OriginalBuildKey.Type.IsGenericType &&
                context.OriginalBuildKey.Type.GetGenericTypeDefinition() == this.baseType)
            {
                // Get generic args
                Type[] argTypes = context.BuildKey.Type.GetGenericArguments();

                if (argTypes.Length == 2 && argTypes.Distinct().Count() == 1)
                {
                    context.BuildKey = new NamedTypeBuildKey(
                        this.identityGenericType.MakeGenericType(argTypes[0]),
                        context.BuildKey.Name);
                }
            }
        }
    }
}

这样做是在类型映射检查之前检查所请求的类型是否是IDataTranslator<T,K>并且有两个泛型参数并且两个泛型参数是相同的类型。如果是,则将IdentityDataTranslator<T>设置为新的构建密钥(而不是预期的DataTranslator<T,K>

缺少是对类型的验证,以确保它们是正确的形状并且可以分配。

最后,设置容器并运行一些测试以确保当from和to类型相同时我们得到IdentityDataTranslator

var container = new UnityContainer();
container.AddExtension(
    new IdentityGenericsExtension(typeof(IdentityDataTranslator<>), typeof(IDataTranslator<,>)));

container.RegisterType(typeof(IDataTranslator<,>), typeof(DataTranslator<,>));

// Since A is different than B we get back a DataTranslator<A,B>
var dataTranslator = container.Resolve<IDataTranslator<A, B>>();
Debug.Assert(dataTranslator.GetType() == typeof(DataTranslator<A, B>));

// Since A is the same as A we get back a IdentityDataTranslator<A>
var identityTranslator = container.Resolve<IDataTranslator<A, A>>();
Debug.Assert(identityTranslator.GetType() == typeof(IdentityDataTranslator<A>));

上述方法有效但可能存在一种严格的基于注册的方法,我没有想到它也可以起作用并强制执行约束。