В вашем вопросе отображение одного набора классов в другой набор классов выглядит довольно просто. Однако часто требуется вызывать определенные конструкторы и/или задавать свойства выходных классов на основе входных классов. Иногда вы можете использовать библиотеку, например AutoMapper.
Однако в других случаях вам необходимо создавать специальные фабричные методы для каждого преобразования. В вашем случае это будут фабричные методы для создания Foo1
из Bar1
, Foo2
из Bar2
и т. д.:
Foo1 CreateFoo1(Bar1 bar1) { ... }
Foo2 CreateFoo2(Bar2 bar2) { ... }
Вы можете сохранить все эти фабричные методы как делегаты в словаре, а затем использовать тип ввода, чтобы выбрать фабрику для создания типа вывода.
var inputType = input.GetType();
var factory = factories[inputType];
var output = factory(input);
Используя отражение, вы можете создать этот словарь, и, чтобы избежать дополнительных затрат на использование отражения при вызове фабрики, вы можете использовать выражения для компиляции небольших лямбда-выражений, которые будут выполнять требуемое приведение и вызов.
Эта функциональность может быть представлена через базовый класс, который предполагает, что типы ввода и вывода находятся в иерархиях параллельных классов. Например, в вашем случае все классы 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
использует отражение для поиска как общедоступных, так и закрытых методов, которые могут создать TOuput
(возможно, производный класс) из TInput
(неабстрактный производный класс).
Метод CreateFactory
создает Func<TInput, TOutput>
, который выполняет требуемое преобразование вниз перед вызовом фабричного метода. После того, как лямбда скомпилирована, ее вызов не требует дополнительных затрат.
Построение класса, производного от TypeBasedFactory
, будет использовать отражение для построения словаря фабрик, поэтому вам следует избегать создания более одного экземпляра (т. е. фабрика должна быть одноэлементной).
person
Martin Liversage
schedule
26.01.2017