如何使用自定义工厂方法注册开放通用? [英] How to register open generic with custom factory method?

查看:43
本文介绍了如何使用自定义工厂方法注册开放通用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

TL; DR :我可以使用Autofac创建一个通用工厂,以便我可以注入IProduct<TModel>而不是在需要的任何地方从IFactory解析它吗?有没有办法将从工厂解决"任务移至合成根目录?

TL;DR: Can I create a generic factory with Autofac, so that I can inject IProduct<TModel> rather than resolving it from IFactory anywhere I need it? Is there a way to move the resolving-from-factory task to the composition root?

因此,我正在使用第三方库,该库公开了一些通过工厂创建的通用接口.出于演示目的,我们假设以下代码是该库:

So I'm using a third party library, which exposes some generic interfaces which are created through a factory. For demonstration purposes, we'll assume that the following code is the library:

第三方库模型:

public interface IFactory
{
    IProduct<TModel> CreateProduct<TModel>(string identifier);
}

internal class Factory : IFactory
{
    private readonly string _privateData = "somevalues";

    public IProduct<TModel> CreateProduct<TModel>(string identifier)
    {
        return new Product<TModel>(_privateData, identifier);
    }
}

public interface IProduct<TModel>
{
    void DoSomething();
}

internal sealed class Product<TModel>: IProduct<TModel>
{
    private readonly string _privateData;
    private readonly string _identifier;

    public Product(string privateData, string identifier)
    {
        _privateData = privateData;
        _identifier = identifier;
    }

    public void DoSomething()
    {
        System.Diagnostics.Debug.WriteLine($"{_privateData} + {_identifier}");
    }
}

我的代码:

还有我的TModel:

public class Shoe { }

现在,让我们假设我要在MyService中使用IProduct<Shoe>.我需要在那里解决它:

Now, let's assume that I want an IProduct<Shoe> in MyService. I need to resolve it there:

public class MyService
{
    public MyService(IFactory factory)
    {
        IProduct<Shoe> shoeProduct = factory.CreateProduct<Shoe>("theshoe");
    }
}

但是如果我能像这样声明鞋子,那会不会更好:

But wouldn't it be nicer if I could declare shoe like this:

public class ProductIdentifierAttribute : System.Attribute
{
    public string Identifier { get; }

    public ProductIdentifierAttribute(string identifier)
    {
        this.Identifier = identifier;
    }
}

[ProductIdentifier("theshoe")]
public class Shoe { }

然后像这样注入它?:

public class MyService
{
    public MyService(IProduct<Shoe> shoeProduct) { }
}

借助Autofac,我可以使用工厂来创建常规的非泛型类,例如:

With Autofac I can use a factory to create regular non-generic classes like so:

builder
    .Register<INonGenericProduct>(context =>
    {
        var factory = context.Resolve<INonGenericFactory>();
        return factory.CreateProduct("bob");
    })
    .AsImplementedInterfaces();

但这不适用于泛型类.我必须使用RegisterGeneric.不幸的是,您传递给RegisterGeneric的类型是开放的 concrete 类型,而不是开放的 interface 类型.我提出了两种解决方法.

But this doesn't work for generic classes. I have to use RegisterGeneric. Unfortunately, the type you pass to RegisterGeneric is the open concrete type, rather than the open interface type. I've come up with two workarounds.

解决方法1 :反映IFactory以提取_privateData(在真实库中,这要复杂一些,涉及访问其他internal方法和类等),然后提供作为OnPreparing中的Autofac参数:

Workaround 1: Reflect IFactory to extract _privateData (in the real library this is somewhat more complicated, and involves accessing other internal methods and classes, etc.) and then supply that as Autofac parameters in OnPreparing:

Type factoryType = typeof(Factory);
Type factoryField = factoryType.GetField("_privateData", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Getfield);
Type productType = typeof(Product); // this is `internal` in the third party library, so I have to look it up from the assembly in reality

builder
.RegisterGeneric(productType)
.OnPreparing(preparing =>
{
    var factory = preparing.Context.Resolve<IFactory>();
    var privateFieldValue = factoryField.GetValue(factory);
    var closedProductType = preparing.Component.Activator.LimitType;
    var productModel = closedProductType.GetGenericArguments().Single();
    var productIdentifier = productModel.GetGenericArgument<ProductIdentifierAttribute>().Identifier;

    preparing.Parameters = new List<Parameter>()
    {
        new PositionalParameter(0, privateFieldValue),
        new PositionalParameter(0, productIdentifier)
    };
})
.AsImplementedInterfaces();

但是显然,由于许多原因,这是一个糟糕的解决方案,最重要的是,它很容易受到库中内部更改的影响.

But clearly this is a terrible solution for numerous reasons, the most significant being that it's vulnerable to internal changes within the library.

解决方法2 :创建一个虚拟类型并将其替换为OnActivating:

Workaround 2: Create a dummy type and substitute it in OnActivating:

public class DummyProduct<TModel> : IProduct<TModel>
{
    public void DoSomething() => throw new NotImplementedException("");
}

因此,我们将其注册为开放泛型,并在注入之前替换其值:

So, we register that as the open generic, and substitute its value before injecting it:

MethodInfo openProductBuilder = this.GetType().GetMethod(nameof(CreateProduct), BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod);
builder
    .RegisterGeneric(typeof(DummyProduct<>))
    .OnActivating(activating => 
    {
        var productModel = activating.Instance.GetType().GetGenericArguments().First();
        var productIdentifier = productModel.GetGenericArgument<ProductIdentifierAttribute>().Identifier;
        var factory = activating.Context.Resolve<IFactory>();
        var closedProductBuilder = openProductBuilder.MakeGenericMethod(productModel);
        object productObject = closedProductBuilder.Invoke(this, new object[] { factory, productIdentifier });
        handler.ReplaceInstance(productObject);
    })
    .AsImplementedInterfaces();

,我们有一个辅助方法,因此我们仅依赖于此Mongo模块类中的反映方法 :

and we have a helper method so that we're only reliant on reflecting methods in this Mongo module class:

private IProduct<TModel> CreateProduct<TModel>(IFactory factory, string identifier)
{
    return factory.CreateProduct<TModel>(identifier);
}

现在,显然,这比第一种方法更好,并且不需要过多的反射.不幸的是,每当我们想要一个真实的对象时,它的确涉及创建一个虚拟对象.糟透了!

Now, clearly this is better than the first method, and doesn't rely on too much reflection. Unfortunately, it does involve creating a dummy object each time we want the real one. That sucks!

问题:还有其他方法可以使用Autofac做到这一点吗?我可以以某种方式创建Autofac可以使用的通用工厂方法吗?我的主要目标是删除创建虚拟类型的步骤,然后直接跳转到调用CreateProduct代码.

Question: Is there another way to do this using Autofac? Can I somehow create a generic factory method that Autofac can use? My main goal is to cut out the creating the dummy type, and skip straight to calling the CreateProduct code.

注意事项:我已经删去了很多错误检查等,通常我会尽力使这个问题尽可能短,同时仍然充分说明问题和当前的解决方案.

Notes: I've cut out a fair bit of error checking, etc. that I would normally do to make this question as short as possible whilst still adequately demonstrating the problem and my current solutions.

推荐答案

如果工厂中没有非通用的Create方法,则需要调用MakeGenericMethod.

If there is no non generic Create method in your factory you will need a call to the MakeGenericMethod.

您可以使用IRegistrationSource组件代替OnActivating事件,该组件的作用与解决方法2相同

Instead of OnActivating event you can use a IRegistrationSource component that will do the same as in your workaround 2

internal class FactoryRegistrationSource : IRegistrationSource
{
    private static MethodInfo openProductBuilder = typeof(Factory).GetMethod(nameof(Factory.CreateProduct));

    public Boolean IsAdapterForIndividualComponents => false;

    public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
    {
        IServiceWithType typedService = service as IServiceWithType;

        if (typedService != null && typedService.ServiceType.IsClosedTypeOf(typeof(IProduct<>)))
        {
            IComponentRegistration registration = RegistrationBuilder.ForDelegate(typedService.ServiceType, (c, p) =>
             {
                 IFactory factory = c.Resolve<IFactory>();

                 Type productModel = typedService.ServiceType.GetGenericArguments().First();
                 String productIdentifier = productModel.GetCustomAttribute<ProductIdentifierAttribute>()?.Identifier;

                 MethodInfo closedProductBuilder = openProductBuilder.MakeGenericMethod(productModel);
                 Object productObject = closedProductBuilder.Invoke(factory, new object[] { productIdentifier });

                 return productObject;
             }).As(service).CreateRegistration();
            yield return registration;
        }
        yield break;
    }
}

这篇关于如何使用自定义工厂方法注册开放通用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆