TL;DR:我可以用 Autofac 创建一个通用工厂,以便我可以注入IProduct<TModel>
而不是从解决它IFactory
我在哪里需要它?有没有办法将工厂解析任务移至组合根?
因此,我使用第三方库,它公开了一些通过工厂创建的通用接口。出于演示目的,我们假设以下代码是库:
第三方库模型:
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}");
}
}
My code:
And my TModel
:
public class Shoe { }
现在,假设我想要一个IProduct<Shoe>
in MyService
。我需要在那里解决它:
public class MyService
{
public MyService(IFactory factory)
{
IProduct<Shoe> shoeProduct = factory.CreateProduct<Shoe>("theshoe");
}
}
但如果我能像这样声明鞋子岂不是更好:
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,我可以使用工厂来创建常规的非泛型类,如下所示:
builder
.Register<INonGenericProduct>(context =>
{
var factory = context.Resolve<INonGenericFactory>();
return factory.CreateProduct("bob");
})
.AsImplementedInterfaces();
但这不适用于泛型类。我必须使用RegisterGeneric
。不幸的是,你传递给的类型RegisterGeneric
是开放的concrete类型,而不是开放界面类型。我想出了两种解决方法。
解决方法1: 反映IFactory
提取_privateData
(在真实的库中,这有点复杂,并且涉及访问其他库internal
方法和类等),然后将其作为 Autofac 参数提供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();
但显然这是一个糟糕的解决方案出于多种原因,最重要的是它很容易受到库内部变化的影响。
解决方法2:创建一个虚拟类型并将其替换为OnActivating
:
public class DummyProduct<TModel> : IProduct<TModel>
{
public void DoSomething() => throw new NotImplementedException("");
}
因此,我们将其注册为开放泛型,并在注入之前替换其值:
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 模块类中:
private IProduct<TModel> CreateProduct<TModel>(IFactory factory, string identifier)
{
return factory.CreateProduct<TModel>(identifier);
}
现在,显然这比第一种方法更好,并且不依赖太多的反射。不幸的是,它确实涉及到每次我们想要真实对象时创建一个虚拟对象。太糟糕了!
Question:还有其他方法可以使用 Autofac 来做到这一点吗?我可以以某种方式创建 Autofac 可以使用的通用工厂方法吗?我的主要目标是取消创建虚拟类型,并直接跳到调用CreateProduct
code.
Notes:我已经删除了一些我通常会做的错误检查等,以使这个问题尽可能短,同时仍然充分展示问题和我当前的解决方案。