C# 使用Is/As运算符的工厂方法

C# 使用Is/As运算符的工厂方法,c#,factory,C#,Factory,我有一个类似以下代码片段的工厂。Foo是Bar的包装类,在大多数情况下(但不是所有情况下),有一个1:1的映射。通常,Bar对Foo一无所知,但Foo以Bar为例。有没有更好/更干净的方法 public Foo Make( Bar obj ) { if( obj is Bar1 ) return new Foo1( obj as Bar1 ); if( obj is Bar2 ) return new Foo2( obj as Bar2 );

我有一个类似以下代码片段的工厂。Foo是Bar的包装类,在大多数情况下(但不是所有情况下),有一个1:1的映射。通常,Bar对Foo一无所知,但Foo以Bar为例。有没有更好/更干净的方法

public Foo Make( Bar obj )
{
    if( obj is Bar1 )
        return new Foo1( obj as Bar1 );
    if( obj is Bar2 )
        return new Foo2( obj as Bar2 );
    if( obj is Bar3 )
        return new Foo3( obj as Bar3 );
    if( obj is Bar4 )
        return new Foo3( obj as Bar4 ); // same wrapper as Bar3
    throw new ArgumentException();
}
乍一看,这个问题可能看起来像是重复的(也许是),但我还没有看到一个完全相同的问题。这里有一个很接近,但不是很接近:


如果这些是引用类型,那么在
is
之后调用
as
是不必要的开销。通常的习惯用法是将
转换为
并检查null

从微观优化退一步来看,您似乎可以使用链接到的文章中的一些技术。具体来说,您可以创建一个键入类型的字典,其值是构造实例的委托。该代理将获取一个(的子项)Bar并返回一个(的子项)Foo。理想情况下,Foo的每个子级都会将自己注册到字典中,而字典在Foo本身中可能是静态的

下面是一些示例代码:

// Foo creator delegate.
public delegate Foo CreateFoo(Bar bar);

// Lookup of creators, for each type of Bar.
public static Dictionary<Type, CreateFoo> Factory = new Dictionary<Type, CreateFoo>();

// Registration.
Factory.Add(typeof(Bar1), (b => new Foo1(b)));

// Factory method.
static Foo Create(Bar bar)
{
    CreateFoo cf;
    if (!Factory.TryGetValue(bar.GetType(), out cf))
        return null;

    return cf(bar);
}
//Foo创建者委托。
公共代表Foo CreateFoo(Bar);
//查找每种类型的栏的创建者。
公共静态字典工厂=新字典();
//注册。
添加(typeof(Bar1),(b=>newfoo1(b));
//工厂法。
静态Foo创建(Bar)
{
CreateFoo-cf;
if(!Factory.TryGetValue(bar.GetType(),out cf))
返回null;
返回cf(巴);
}

我不确定你到底想要实现什么。我可能会尝试让它更通用

您可以在Foo上使用它支持的属性,然后在初始化阶段创建一个列表。我们正在做很多类似的事情,这使得添加和连接新类非常容易

private Dictionary<Type, Type> fooOfBar = new Dictionary<Type, Type>();
public initialize()
{
  // you could scan all types in the assembly of a certain base class
  // (fooType) and read the attribute

  fooOfBar.Add(attribute.BarType, fooType);
}

public Foo Make( Bar obj )
{
  return (Foo)Activator.CreateInstance(fooOfBar(obj.GetType()), obj);
}
private Dictionary fooOfBar=new Dictionary();
公共初始化()
{
//您可以扫描某个基类的程序集中的所有类型
//(fooType)并读取属性
添加(attribute.BarType,fooType);
}
公共食品制造(Bar obj)
{
return(Foo)Activator.CreateInstance(fooOfBar(obj.GetType()),obj);
}

在您的问题中,从一组类到另一组类的映射看起来非常简单。但是,通常需要根据输入类调用特定的构造函数和/或设置输出类的属性。有时你可以使用像这样的库

但是,在其他情况下,您需要为每次转换创建特定的工厂方法。在您的情况下,可以使用工厂方法从
Bar1
创建
Foo1
,从
Bar2
等创建
Foo2

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
作为基类。但是,如果不是这样,那么所有类都将
对象作为基类,因此这种方法仍然有效

派生的factory类将如下所示:

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
的代码:

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);
    }
}
公共抽象类TypeBasedFactory
where TInput:class where TOutput:class
{
私人只读词典工厂;
受保护的TypeBasedFactory()
{
工厂=创建工厂();
}
私有字典createFactorys()
{
返回GetType()
.GetMethods(
BindingFlags.Public
|BindingFlags.NonPublic
|BindingFlags.Instance)
.Where(methodInfo=>
!methodInfo.IsAbstract
&&methodInfo.GetParameters().Length==1
&&typeof(TOutput).IsAssignableFrom(methodInfo.ReturnType))
.选择(methodInfo=>new
{
MethodInfo=MethodInfo,
methodInfo.GetParameters().First().ParameterType
})
.其中(工厂=>
typeof(TInput).IsAssignableFrom(factory.ParameterType)
&&!factory.ParameterType.IsaStract)
.ToDictionary(
factory=>factory.ParameterType,
factory=>CreateFactory(factory.MethodInfo,factory.ParameterType));
}
private Func CreateFactory(MethodInfo MethodInfo,类型参数类型)
{
//创建此函数:(t输入)=>方法((参数)输入)
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(调用表达式,输入表达式);
返回lambdaExpression.Compile();
}
公共TOutput CreateFrom(TInput输入)
{
如果(输入==null)
抛出新ArgumentNullException(nameof(input));
var inputType=input.GetType();
Func工厂;
如果(!factories.TryGetValue(输入类型,出厂))
抛出新的InvalidOperationException($“没有为{inputType.FullName}定义工厂方法”);
返回工厂(输入);
}
}
createFactorys
方法使用反射来查找能够从
TInput
(非抽象派生类)创建
TOuput
(可能是派生类)的公共和私有方法

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);
    }
}