在您的问题中,从一组类到另一组类的映射看起来很简单。但是,您通常希望根据输入类调用特定的构造函数和/或设置输出类的属性。有时你可以使用像AutoMapper 这样的库。
但是,在其他情况下,您需要为每次转换创建特定的工厂方法。在您的情况下,从Bar1、Foo2 从Bar2 等创建Foo1 将是工厂方法:
Foo1 CreateFoo1(Bar1 bar1) { ... }
Foo2 CreateFoo2(Bar2 bar2) { ... }
您可以将所有这些工厂方法作为委托存储在字典中,然后使用输入类型选择工厂来创建输出类型。
var inputType = input.GetType();
var factory = factories[inputType];
var output = factory(input);
通过使用反射,您可以构建这个字典,并避免在调用工厂时使用反射的额外成本,您可以使用表达式来编译小型 lambda,然后执行所需的转换和调用。
可以通过假设输入类型和输出类型在并行类层次结构中的基类公开此功能。例如,在您的情况下,所有 Foo# 类可能将 Foo 作为基类,而所有 Bar# 类可能将 Bar 作为基类关闭。但是,如果不是这种情况,那么所有类都将object 作为基类,所以这种方法仍然有效。
您的派生工厂类将如下所示:
public class FooFactory : TypeBasedFactory<Bar, Foo>
{
private Foo1 CreateFoo1(Bar1 bar1)
{
return new Foo1(bar1.Id, bar1.Name, ...);
}
private Foo2 CreateFoo2(Bar2 bar2)
{
return new Foo2(bar2.Description, ...);
}
}
注意工厂方法是私有的。它们不打算被直接调用。相反,TypeBasedFactory 声明了一个 CreateFrom 方法,该方法将调用正确的工厂:
var fooFactory = new TypeBasedFactory<Bar, Foo>();
var foo = fooFactory.CreateFrom(bar);
这是TypeBasedFactory的代码:
public abstract class TypeBasedFactory<TInput, TOutput>
where TInput : class where TOutput : class
{
private readonly Dictionary<Type, Func<TInput, TOutput>> factories;
protected TypeBasedFactory()
{
factories = CreateFactories();
}
private Dictionary<Type, Func<TInput, TOutput>> CreateFactories()
{
return GetType()
.GetMethods(
BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Instance)
.Where(methodInfo =>
!methodInfo.IsAbstract
&& methodInfo.GetParameters().Length == 1
&& typeof(TOutput).IsAssignableFrom(methodInfo.ReturnType))
.Select(methodInfo => new
{
MethodInfo = methodInfo,
methodInfo.GetParameters().First().ParameterType
})
.Where(factory =>
typeof(TInput).IsAssignableFrom(factory.ParameterType)
&& !factory.ParameterType.IsAbstract)
.ToDictionary(
factory => factory.ParameterType,
factory => CreateFactory(factory.MethodInfo, factory.ParameterType));
}
private Func<TInput, TOutput> CreateFactory(MethodInfo methodInfo, Type parameterType)
{
// Create this Func<TInput, TOutput>: (TInput input) => Method((Parameter) input)
var inputExpression = Expression.Parameter(typeof(TInput), "input");
var castExpression = Expression.Convert(inputExpression, parameterType);
var callExpression = Expression.Call(Expression.Constant(this), methodInfo, castExpression);
var lambdaExpression = Expression.Lambda<Func<TInput, TOutput>>(callExpression, inputExpression);
return lambdaExpression.Compile();
}
public TOutput CreateFrom(TInput input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
var inputType = input.GetType();
Func<TInput, TOutput> factory;
if (!factories.TryGetValue(inputType, out factory))
throw new InvalidOperationException($"No factory method defined for {inputType.FullName}.");
return factory(input);
}
}
CreateFactories 方法使用反射来查找能够从 TInput(非抽象派生类)创建 TOuput(可能是派生类)的公共和私有方法。
CreateFactory 方法创建一个Func<TInput, TOutput>,在调用工厂方法之前执行所需的向下转换。一旦 lambda 被编译,调用它就没有反射开销。
构造一个派生自TypeBasedFactory 的类将使用反射来构建工厂字典,因此您应该避免创建多个实例(即,工厂应该是单例)。