65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# 中的表达式树实现工厂方法模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.47/5 (7投票s)

2012年5月14日

CPOL

3分钟阅读

viewsIcon

24597

downloadIcon

5

使用表达式树在 C# 中高效实现工厂方法模式。

介绍  

嗯,写这篇文章的唯一目的是不告诉您什么是工厂类或工厂方法模式。

我敢肯定,网络上已经有许多更知名的文章了。

相反,我试图通过使用表达式树而不是反射来实现工厂类的一种新的高效方法。 

背景         

尝试阅读关于使用 C# 实现工厂方法模式的文章。  

这是一篇可以帮助您入门的文章:  

https://codeproject.org.cn/Articles/9319/Creating-a-Class-Factory-with-C-and-NET 

使用代码

好的,那么让我们先了解一下如何使用反射来创建一个对象,其中包含工厂方法模式。 

这是我们的示例工厂类图: 

嗯,为了创建一个简单的基于反射的工厂类,用于根据类型名称创建我们的衬衫类型,需要做的是:

  1. 创建一个字典,其中包含衬衫类型名称 [String] 作为键,衬衫类型 [Type] 作为值。  

  2. 使用 Activator (System.Activator) 为提供的衬衫类型名称创建 Type 实例。 

以下是代码: 

public Shirt CreateShirt(string shirtType, Color color, string brand)
{
if (!_types.ContainsKey(shirtType)) 
    return null;

var t = _types[shirtType];
return Activator.CreateInstance(t, color, brand) as Shirt;
}

System.Activator 的问题 

嗯,您可能会想上面的代码有什么问题,让我来告诉您 Activator 的 CreateInstance 是如何工作的,或者实际上是如何创建实例的,以便理解为什么它很慢并且需要被替换。 

CreateInstance 方法的定义如下: 

public static object CreateInstance(Type type, params object[] args);

第一个参数是要让激活器创建对象的类型。在内部,激活器将从其名称在元数据中搜索此类型。   

下一个参数是带有 params 关键字的对象数组,这意味着您可以在不创建数组的情况下提供参数。 

在内部,激活器将尝试在该类型中搜索构造函数,其参数的数量、顺序和类型与通过 CreateInstance 方法以对象数组形式提供的参数匹配。 

如果 args 数组是空数组或 null,则调用不带参数的构造函数(默认构造函数)。 

现在,由于它通过反射搜索类型,而反射在内部始终通过字符串搜索,因此在运行时会变慢。 

对于上面的衬衫工厂,在我的 Core 2 Duo、4GB 内存的机器上,当我运行 CreateShirt 方法一百万次时,花费了大约 4.74 秒。 

sysActFactory.CreateShirt("Tee", Color.Blue, "Reebok");

现在让我们看看解决方案

表达式树来拯救  

好的,表达式树在这种情况下是如何提供帮助的? 

如果您尝试分析 activator 的 CreateInstance 方法,您会注意到它每次在调用对象之前都需要进行元数据搜索,这使得它变慢。 

所以我想,如果我能通过在编译时创建表达式来替换这个元数据搜索,那么我只需要在运行时调用表达式。 

嗯,这很简单,为什么不在编译时提供正确的构造函数,然后用它来创建表达式。我们可以在运行时调用这个表达式,这就像直接调用构造函数一样。 

有道理吗?让我们看看…… 

让我们从拥有一个与 CreateInstance 具有相同签名的自定义 Activator 委托开始。

public delegate object Activator(params object[] args);

我们将使用这个委托将我们的表达式转换为并从我们的 Activator Creation Method 返回。

该方法如下所示:

       
        private static TDelegate DoGetActivator<TDelegate>(ConstructorInfo ctor)  
        where TDelegate : class
        {
            var ctorParams = ctor.GetParameters();
            var paramExp = Expression.Parameter(typeof (object[]), "args");

            var expArr = new Expression[ctorParams.Length];

            for (var i = 0; i < ctorParams.Length; i++)
            {
                var ctorType = ctorParams[i].ParameterType;
                var argExp = Expression.ArrayIndex(paramExp, Expression.Constant(i));
                var argExpConverted = Expression.Convert(argExp, ctorType);
                expArr[i] = argExpConverted;
            }

            var newExp = Expression.New(ctor, expArr);
            var lambda = Expression.Lambda<TDelegate>(newExp, paramExp);            
            return lambda.Compile();
        }

接下来,您可以将其包装在扩展方法中,并将 TDelegate 类型替换为 Activator 类型。 

该方法如下所示: 

public static Activator GetActivator(this ConstructorInfo ctor)
{
   return DoGetActivator<Activator>(ctor);
}

您可以拥有另一个看起来像这样的 Type 扩展:

public static Activator GetActivator(this Type type)
        {
            var ci = type.GetConstructors(BindingFlags.Instance |BindingFlags.Public).FirstOrDefault();

            return ci.GetActivator();
        }

我们还可以创建另一个扩展到 Activator 类型,以实际调用构造函数,如下所示:

public static T CreateInstance<T>(this Activator activator, params object[] args) where T : class
        {
            return activator(args) as T;
        }

这是调用上述方法集以创建 PartyWear Shirt 实例的方法: 

var type = typeof (PartyWear);
var activator = type.GetActivator();

var shirt = activator.CreateInstance<Shirt>(Color.Blue, "Blackberry");

现在,让我们运行最终测试。 

我们将创建两个工厂,一个使用 System Activator,另一个使用 Expression Tree Activator,看看区别: 

 

嗯,嗯,嗯,您可以看到区别。

使用表达式树的工厂在半秒内制作了一百万件 T 恤,而 System Activator 花费了惊人的 4.7 秒。

好了,各位,下载附件中的代码并玩玩吧。

编程愉快!!! 

© . All rights reserved.