方法工厂 – 案例与反思

前几天我遇到了一些代码,我想知道这是否是最好的方法。 我们有一个方法,从一些Web表单数据中获取一个字符串,根据传入的字符串对某个对象执行操作。目前,它使用reflection来计算要采取的操作,但我想知道switch语句是否会更好。

例:

编辑:我为Lucerno指出的代表添加了第三个选项

public class ObjectManipulator { private void DoX(object o) { } private void DoY(object o) { } private void DoZ(object o) { } public void DoAction(string action, object o) { switch (action) { case "DoX": DoX(o); break; case "DoY": DoY(o); break; case "DoZ": DoZ(o); break; default: throw new Exception(string.Format( "Cannot locate action:{0}", action)); } } public void DoActionViaReflection(string action, object o) { MethodInfo method = typeof(ObjectManipulator). GetMethod(action, new Type[] { typeof(object) }); if (method == null) { throw new Exception(string.Format( "Cannot locate action:{0}", action)); } else { method.Invoke(this, new object[] { o }); } } private Dictionary<string, Action> _methods; public ObjectManipulator() { _methods = new Dictionary<string, Action>() { {"DoX", o => DoX(o)}, {"DoY", o => DoY(o)}, {"DoZ", o => DoZ(o)} }; } public void DoActionViaDelegates(string action, object o) { if (!_methods.ContainsKey(action)) { throw new Exception(string.Format( "Cannot locate action:{0}", action)); } else { _methods[action](o); } } } 

第一个例子使用了开关,你可以看到它可能非常冗长。 第二个更短,但使用reflection,我知道有些人像瘟疫一样避免。

一种方法的表现会明显好于另一种吗?

如果有100个不同的行动而不仅仅是3个,那么性能会改变吗?

如果你正在阅读它,你更愿意在你的代码中看到哪一个?

第一种情况几乎总是更快。 但是,它的性能来自于它可以在编译期间提前绑定,但这也是它的最大缺点:例如,这种方法不能处理动态加载的程序集,而且它更容易出错,因为它是命令性而非陈述性。 (例如,忘记新实现的操作可能会很快发生。)

我通常的方法是在发现时使用reflection来实现这样的模式,但是在调用时使用委托。 这为您提供了reflection方法的灵活性,其性能非常接近早期方法。

  • 发现阶段:使用reflection来查找成员(使用属性,接口,签名和/或编码约定)。 在您的情况下,您始终具有相同的签名,因此要使用的委托将是Action 。 将这些成员添加到Dictionary>实例,使用CreateDelegate()MethodInfo创建委托。

  • 调用阶段:通过其密钥获取委托并调用它,这非常简单(这里假设字典称为methods ): methods[action](o)

除非您对其进行分析并且它是瓶颈,否则性能不应该是您的关注点。 更重要的是IMO,你在reflection版本中失去了静态类型安全性和分析。 编译时无法检查是否正在调用这些操作方法DoXDOY等。 这对您来说可能是也可能不是问题,但这将是我最关心的问题。

此外,对于reflection版本的性能,动作的数量完全不重要。 当你class上有很多成员时, GetMethod不会变慢。

你可以这样解决@ user287107的答案:

 var Actions = new Dictionary>(); Actions["DoX"] = DoX; Actions["DoY"] = DoY; Actions["DoZ"] = DoZ; 

这实际上是明确地执行@Lucero答案的发现阶段,如果名称不总是匹配,这可能很有用。

如果可能的话,在枚举中定义一组动作也会很好 – 这样会快一点,因为你不需要散列字符串。 它还可以让您编写unit testing,以确保您没有错过任何潜在的值。

如何使用代表? 你可以使用这样的东西:

 var Actions = new Dictionary>(); Actions["DoX"] = x => DoX(x); Actions["DoY"] = x => DoY(x); Actions["DoZ"] = x => DoZ(x); 

然后

 public void DoAction(string action, object o) { Actions[action](o); } 

如果你有很多情况,我想这表现得更好,因为字典有哈希查找。

如果用户可以提供另一个函数名称,您使用的reflection类型可能会产生安全问题