方法工厂 - 案例与反思

时间:2010-11-20 19:02:32

标签: c# reflection switch-statement factory

前几天我遇到了一些代码,我想知道这是否是最好的方法。我们有一个方法,它根据传入的字符串从一些Web表单数据中获取一个字符串对象。当前,它使用反射来计算要采取的操作,但我想知道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<object>> _methods;
    public ObjectManipulator()
    {
        _methods = new Dictionary<string, Action<object>>()
        {
            {"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);
        }
    }

}

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

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

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

如果您正在阅读它,您还会在代码中看到哪些内容?

4 个答案:

答案 0 :(得分:4)

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

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

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

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

答案 1 :(得分:3)

如何使用代表?你可以使用类似的东西:

var Actions = new Dictionary<String, Action<object>>();
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);
}
如果你有很多情况,我猜这样做会更好,因为字典有哈希查找。

如果用户可以提供另一个函数名称

,您使用的反射类型可能会产生安全问题

答案 2 :(得分:2)

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

此外,对于反射版本的性能,操作的数量完全无关紧要。当你班上有很多成员时,GetMethod不会减速。

答案 3 :(得分:2)

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

var Actions = new Dictionary<String, Action<object>>();
Actions["DoX"] = DoX;
Actions["DoY"] = DoY;
Actions["DoZ"] = DoZ;

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

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