【问题标题】:How to resolve an instance of a closed generic type from a type instance and use it?如何从类型实例解析封闭泛型类型的实例并使用它?
【发布时间】:2020-06-11 09:47:50
【问题描述】:

假设您有几个开放的通用抽象类:

public abstract class AbstractActionHandler<TInput>
{
    public abstract Task Action(TInput input);
}

public abstract class AbstractFunctionHandler<TInput, TOutput>
{
    public abstract Task<TOutput> Function(TInput input);
}

那么你有几个实现:

public class IntActionClass : AbstractActionHandler<int>
{
    public Task Action(int input)
    {
        //do some work...
    }
}
public class StringActionClass : AbstractActionHandler<string>
{
    public Task Action(string input)
    {
        //do some work...
    }
}
public class IntFunctionClass : AbstractFunctionHandler<int, string>
{
    public Task<string> Function(int input)
    {
        //do some work...
    }
}
public class StringFunctionClass : AbstractFunctionHandler<string, bool>
{
    public Task<bool> Function(string input)
    {
        //do some work...
    }
}

现在您想在 IoC 容器中注册程序集中的所有实现,以便解决依赖关系:

var builder = new ContainerBuilder();
var openGenericTypes = new[]
{
    typeof(AbstractActionHandler<>),
    typeof(AbstractFunctionHandler<,>)
};

var assembly = Assembly.GetExecutingAssembly();
var implementationTypes = new List<Type>();

foreach (var openGenericType in openGenericTypes)
{
    var types = assembly.GetTypes()
        .Where(type => type.IsAssignableFrom(openGenericType))
        .Where(type => !type.IsAbstract);

    builder.RegisterTypes(types.ToArray())
        .AsClosedTypesOf(openGenericType)
        .InstancePerDependency();

    implementationTypes.AddRange(types);
}

var container = builder.Build();

现在您有一个 implementationTypes 列表,并且您想要遍历该列表并将每个类型的实例解析为它们的封闭泛型类型。这就是问题发生的地方:

foreach (var type in implementationTypes)
{
    var handler = container.Resolve(type); 
    // handler is of type object but needs to be of its closed generic type
    // ie. AbstractAbstractHandler<int>

    if (type == typeof(AbstractActionHandler<>))
    {
        // This should be called for all classes closing AbstractActionHandler<>
        ConstructActionHandler<TInput>(handler); // calls AbstractActionHandler.Action
    }
    else if (type == typeof(AbstractFunctionHandler<,>))
    {
        // This should be called for all classes closing AbstractFunctionHandler<>
        ConstructFunctionHandler<TInput, TOutput>(handler); // calls AbstractFunctionHandler.Function
    }
}

public void ConstructActionHandler<TInput>(AbstractActionHandler<TInput> handler)
{
    var actionHandlerWrapperDelegate = new Func<TInput, Task>(async (input) =>
    {
        await actionHandler.Action(input);
    });
    // ....
}

所以第一个问题是:如何从类型实例中解析出封闭泛型类型的实例?

我想过创建一个通用的包装类,它可以用 Activator.CreateInstance() 实例化,并让包装器解析正确的处理程序,但我不确定这是否是正确的方法。

此外,假设您想要一个不同通用参数类型的处理程序列表:

// this would not be possible
var actionHandlers = new List<AbstractActionHandler<TInput>>();

// this would however be possible
var actionHandlers = new List<IActionHandler>();
var functionHandlers = new List<IFunctionHandler>();

你想通过它们的类型参数以某种方式连接它们:

foreach (var actionHandler in actionHandlers)
{
    var actionHandlerTypeArgument = handler.GetType().GenericTypeArguments.First();
    var functionHandler = functionHandlers.Find(handler => 
            handler.GetType().GenericTypeArguments.First() == actionHandlerTypeArgument);

    var wrapper = new Func<TInput>(async input => 
    {
        var output = functionHandler.Function(input);
        actionHandler.Action(output);
    });
    // ...
}

如何将 IActionHandler 或 IFunctionHandler 的实例向下转换为其封闭的通用接口?

这个问题的答案可能与第一个问题的答案相同。我只是将它们分开,因为解决第一个问题的方法可能是错误的方法。

任何类型的输入都将受到高度赞赏! 如果有更好的方法,我也很高兴听到。

谢谢!

【问题讨论】:

    标签: c# generics reflection dependency-injection autofac


    【解决方案1】:

    这种形式的非构造泛型类型(typeof(AbstractActionHandler&lt;&gt;)typeof(AbstractFunctionHandler&lt;,&gt;))称为泛型类型定义。您可以使用反射来确定类型是否在其基类之一中具有特定的泛型类型定义。例如,

        private static bool HasGenericBase(Type type, Type baseGenericTypeDefinition)
        {
            if (!baseGenericTypeDefinition.IsGenericTypeDefinition)
                throw new ArgumentException($"The specified type ({baseGenericTypeDefinition}) is not a generic type definition");
            while (type.BaseType != typeof(object))
            {
                type = type.BaseType;
                if (type.IsGenericTypeDefinition && type == baseGenericTypeDefinition)
                {
                    return true;
                }
                if (type.IsGenericType && type.GetGenericTypeDefinition() == baseGenericTypeDefinition)
                {
                    return true;
                }
            }
            return false;
        }
    

    你可以像这样在你的模型中使用它:

            foreach (var type in implementationTypes)
            {
                var handler = container.Resolve(type);
                // handler is of type object but needs to be of its closed generic type
                // ie. AbstractAbstractHandler<int>
    
                if (HasGenericBase(type, typeof(AbstractActionHandler<>)))
                {
                    // This should be called for all classes closing AbstractActionHandler<>
                    ConstructActionHandler<TInput>(handler); // calls AbstractActionHandler.Action
                }
                else if (HasGenericBase(type, typeof(AbstractFunctionHandler<,>)))
                {
                    // This should be called for all classes closing AbstractFunctionHandler<>
                    ConstructFunctionHandler<TInput, TOutput>(handler); // calls AbstractFunctionHandler.Function
                }
            }
    

    【讨论】:

      【解决方案2】:

      我认为您可能希望避免存储抽象类型的实现列表(这就是容器的用途)。

      我想象在您的程序中的某个时刻,您想访问AbstractActionHandler&lt;TInput&gt; 的实现,无论它是什么,并使用它。

      如果您向 Autofac 询问 AbstractiActionHandler&lt;int&gt;AbstractActionHandler&lt;string&gt;,它会确定要使用哪个操作实现,并将其返回给您。

      我在这里有一个例子可以解释我的意思:

      static async Task Main(string[] args)
      {
          var builder = new ContainerBuilder();
          var assembly = Assembly.GetExecutingAssembly();
      
          builder.RegisterAssemblyTypes(assembly)
              .AsClosedTypesOf(typeof(AbstractActionHandler<>));
      
          builder.RegisterAssemblyTypes(assembly)
              .AsClosedTypesOf(typeof(AbstractFunctionHandler<,>));
      
          var container = builder.Build();
      
          // At some point later...
          await InvokeActionHandler(container, 100);
      
          // or...
          await InvokeActionHandler(container, "something");
      }
      
      private static Task InvokeActionHandler<TInput>(IContainer container, TInput inputArg)
      {
          var handler = container.Resolve<AbstractActionHandler<TInput>>();
      
          return handler.Action(inputArg);
      }
      

      您可以使用相同的方法获取AbstractionFunctionHandler&lt;TInput, TOutput&gt;

      至于如何将它们链接在一起,如果我们不想过多地更改您的基类,这会有点复杂;一种选择可能是在Metadata 中附加函数注册的输出类型,并声明一个仅对函数输入进行一般类型化的接口。

      然后您可以在运行时确定您需要的操作类型(例如,在包装器类型中)并解析它。

      类似这样的:

      class Program
      {
      
          static async Task Main(string[] args)
          {
              var builder = new ContainerBuilder();
              var assembly = Assembly.GetExecutingAssembly();
      
              builder.RegisterAssemblyTypes(assembly)
                  .AsClosedTypesOf(typeof(AbstractActionHandler<>));
      
              builder.RegisterAssemblyTypes(assembly)
                  .AsClosedTypesOf(typeof(AbstractFunctionHandler<,>))
                  // Registering a second service here.
                  .AsClosedTypesOf(typeof(IDynamicFunctionHandler<>))
                  // Capturing the output type argument (this is not robust if there are multiple inheritance levels).
                  .WithMetadata("output", t => t.BaseType.GenericTypeArguments[1]);
      
              // Register an open-generic registration wrapper.
              builder.RegisterGeneric(typeof(FuncActionCombo<>));
      
              var container = builder.Build();
      
              await InvokeActionFromFunctionResult(container, 100);
          }
      
          private static async Task InvokeActionFromFunctionResult<TInput>(IContainer container, TInput input)
          {
              // Resolve our combo class (that is only typed to the input).
              var funcHandler = container.Resolve<FuncActionCombo<TInput>>();
      
              await funcHandler.Invoke(input);
          }
      }
      
      public interface IDynamicFunctionHandler<TInput>
      {
          Task<object> FunctionDynamic(TInput input);
      }
      
      public abstract class AbstractFunctionHandler<TInput, TOutput> : IDynamicFunctionHandler<TInput>
      {
          public abstract Task<TOutput> Function(TInput input);
      
          // Just wrap the regular function.
          public async Task<object> FunctionDynamic(TInput input) => await Function(input);
      }
      
      public class FuncActionCombo<TInput>
      {
          private readonly IDynamicFunctionHandler<TInput> _funcHandler;
          private readonly object _actionHandler;
          private readonly MethodInfo _actionInvokeMethod;
      
          public FuncActionCombo(IComponentContext context, Meta<IDynamicFunctionHandler<TInput>> handlerWithMeta)
          {
              _funcHandler = handlerWithMeta.Value;
      
              // Get the output type data we attached to the registration.
              var outputType = (Type) handlerWithMeta.Metadata["output"];
      
              // Create a closed type of the action handler from the output type.
              var closedActionHandler = typeof(AbstractActionHandler<>).MakeGenericType(outputType);
      
              // Resolve the implementation.
              _actionHandler = context.Resolve(closedActionHandler);
      
              // Get the method declaration.
              _actionInvokeMethod = closedActionHandler.GetMethod(nameof(AbstractActionHandler<object>.Action));
          }
      
          public async Task Invoke(TInput input)
          {
              var result = await _funcHandler.FunctionDynamic(input);
      
              // Invoke our dynamic Action.
              await (Task) _actionInvokeMethod.Invoke(_actionHandler, new[] { result });
          }
      }
      

      它仍然不是很好,但其他任何事情都可能需要从根本上重新考虑你是如何处理这个问题的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-10-03
        • 2011-04-02
        • 1970-01-01
        • 2020-06-11
        相关资源
        最近更新 更多