代码之家  ›  专栏  ›  技术社区  ›  Seattle Leonard

方法工厂-案例与反射

  •  6
  • Seattle Leonard  · 技术社区  · 14 年前

    前几天我遇到了一些代码,我想知道这是否是最好的方法。我们有一个方法,它从一些Web表单数据A中提取一个字符串,并根据传入的字符串对对象执行某些操作。目前,它使用反射来计算要采取的操作,但我想知道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 回复  |  直到 14 年前
        1
  •  4
  •   Lucero    14 年前

    第一种情况几乎总是更快。然而,它的性能来自这样一个事实,即它可以在编译期间提前绑定,但这也是它最大的缺点:例如,这种方法不能处理动态加载的程序集,而且它更容易出错,因为它是必需的而不是声明性的。(例如,忘记新执行的操作是一件很快就会发生的事情。)

    我通常的方法是在发现期间使用反射来实现这种模式,但在调用时使用委托。这使得反射方法的灵活性与性能非常接近于早期绑定方法。

    • 发现阶段:使用反射来查找成员(使用属性、接口、签名和/或编码约定)。在您的案例中,您总是拥有相同的签名,因此要使用的委托将是 Action<object> . 将这些成员添加到 Dictionary<string, Action<object>> 实例,从 MethodInfo 使用 CreateDelegate() .

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

        2
  •  3
  •   user287107    14 年前

    使用代理怎么样?你可以用这样的方法:

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

    我想如果你有很多情况的话,这样做会更好,因为字典有散列查找。

    如果用户可以提供另一个函数名,则使用的反射类型可以创建安全问题。

        3
  •  2
  •   Kirk Woll    14 年前

    性能不应该成为您的关注点,除非您分析它并且它是一个瓶颈。更重要的是,IMO,在反射版本中会丢失静态类型安全性和分析。在编译时无法检查这些操作方法 DoX , DOY 等正在被调用。这对你来说可能是一个问题,也可能不是,但那将是我最大的担心。

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

        4
  •  2
  •   t0rx    14 年前

    您可以修复@user287107的答案:

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

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

    如果可能的话,在枚举中定义一组操作也是很好的——这将稍微快一点,因为您不需要散列字符串。它还可以让您编写一个单元测试,以确保没有遗漏任何潜在的值。