委托会员超过经典function的优点或缺点是什么?

class my_class { public int add_1(int a, int b) {return a + b;} public func add_2 = (a, b) => {return a + b;} } 

add_1是一个函数,而add_2是一个委托。 但是在这种情况下,代表们可以填补类似的角色。

由于先例和语言的设计,C#方法的默认选择应该是函数。

然而,这两种方法都有利有弊,所以我已经制作了一份清单。 这两种方法都有任何优点或缺点吗?

传统方法的优点。

  • 更常规
  • 函数的外部用户看到命名参数 – 对于add_2语法arg_n和类型一般信息不够。
  • 使用intellisense-ty Minitech可以更好地工作
  • 适用于reflection – ty Minitech
  • inheritance – Eric Lippert
  • 有一个“这个” – ty CodeInChaos
  • 更低的开销,速度和内存 – ty Minitech和CodeInChaos
  • 在更改和使用函数方面,不需要考虑public \ private。 – ty CodeInChaos
  • 动态性较低,允许在编译时不知道的内容 – ty CodeInChaos

“委托类型字段”方法的优点。

  • 更一致,而不是成员函数和数据成员,它只是数据成员。
  • 可以向外看,表现得像一个变量。
  • 将它存放在容器中效果很好。
  • 多个类可以使用相同的函数,就好像它是每个成员函数一样,这将是非常通用的,简洁的并且具有良好的代码重用。
  • 直接在任何地方使用,例如作为本地function。
  • 可能在垃圾收集传递时效果很好。
  • 更加动态,在编译时必须知道的更少,例如,可能存在在运行时配置对象行为的函数。
  • 就像封装它的代码一样,可以组合并重新编写, msdn.microsoft.com / en-us / library / ms173175%28v = vs.80%29.aspx
  • 该函数的外部用户可以看到未命名的参数 – 有时这是有用的,尽管能够命名它们会很好。
  • 可以更紧凑,在这个简单的例子中,例如可以删除返回,如果有一个参数,也可以删除括号。
  • 滚动你自己的行为,如inheritance – ty Eric Lippert
  • 其他考虑因素,如function,模块化,分布式(代码编写,测试或代码推理)等……

请不要投票结束,那已经发生了,并重新开放。 这是一个有效的问题,即使你不认为委托方法有多大的实际用途,因为它与既定的编码风格有冲突,或者你不喜欢代表的优点。

首先,关于这个设计决定,我的“高阶位”将是我永远不会用公共领域/方法做这种事情。 至少我会使用一个属性 ,甚至可能不是。

对于私有字段,我经常使用这种模式,通常是这样的:

 class C { private Func ActualFunction = (int y)=>{ ... }; private Func Function = ActualFunction.Memoize(); 

现在我可以非常轻松地测试不同记忆策略的性能特征,而无需改变ActualFunction的文本。

“方法是委托类型”策略的另一个优点是,您可以实现与我们“融入”该语言的代码共享技术不同的代码共享技术。 委托类型的受保护字段本质上是一种虚方法 ,但更灵活。 派生类可以用他们想要的任何东西替换它,并且你已经模拟了一个常规的虚拟方法。 但是你可以构建自定义inheritance机制; 例如,如果您真的喜欢原型inheritance,那么您可以有一个约定,如果该字段为null,则会调用某个原型实例上的方法,依此类推。

方法的主要缺点是代理字段类型方法,当然,重载不再有效。 字段名称必须是唯一的; 方法在签名中必须是唯一的。 此外,您没有像我们获得generics方法那样获得generics字段,因此方法类型推断停止工作。

在我看来,第二个提供的优势完全没有第一个优势。 它的可读性低得多,可能效率较低(假设必须隐含Invoke )并且根本不简洁。 更重要的是,如果你使用reflection它不会显示为一个方法,所以如果你这样做来替换每个类中的方法,你可能会破坏看起来应该工作的东西。 在Visual Studio中,IntelliSense将不包含该方法的描述,因为您不能将XML注释放在委托上(至少,与将它们放在普通方法上的方式不同)并且您不知道它们是什么无论如何,除非它是只读的(但如果构造函数改变了它会怎么样?)并且它将显示为字段,而不是方法,这是令人困惑的。

你应该真正使用lambdas的唯一方法是需要关闭的方法,或者它提供了显着的便利优势。 否则,你只是降低了可读性(基本上是我的第一段与当前段的可读性),并且破坏了与以前版本的C#的兼容性。

为什么你应该默认避免委托作为方法,以及什么是替代方案:

学习曲线

以这种方式使用代表会让很多人感到惊讶。 不是每个人都可以围绕代表,或者为什么要换掉function。 似乎有一个学习曲线。 一旦你通过它,代表似乎很简单。

性能和可靠性

以这种方式调用委托会有性能损失。 这是我默认传统方法声明的另一个原因,除非它在我的模式中启用了一些特殊的东西。

还存在执行安全问题。 公共字段可以为空。 如果您传递了具有公共字段的类的实例,则在使用它之前必须检查它是否为空。 这伤害了穿孔,有点蹩脚。

您可以通过将所有公共字段更改为属性(无论如何都是所有.Net编码标准中的规则)来解决此问题。 然后在setter中抛出一个ArgumentNullException如果有人试图赋值null

程序设计

即使你可以处理所有这些,允许方法变得可变也违背了静态OO和函数式编程语言的许多设计。

在静态OO类型中始终是静态的,并且通过多态来启用动态行为。 您可以根据其运行时类型了解类型的确切行为。 这在调试现有程序时非常有用。 允许在运行时修改类型会损害这一点。

在静态OO和函数编程范例中,限制和隔离副作用是非常有用的 ,使用完全不可变结构是实现此目的的主要方法之一。 将方法暴露为委托的唯一要点是创建可变结构,这具有完全相反的效果。

备择方案

如果你真的想要总是使用委托来替换方法,你应该使用像IronPython这样的语言或者在DLR之上构建的其他东西。 这些语言将根据您尝试实施的范例进行调整和调整。 代码的用户和维护者不会感到惊讶。

话虽如此,有些用法可以certificate使用代理作为方法的替代。 您不应该考虑此选项,除非您有令人信服的理由这样做会覆盖这些性能,混淆,可靠性和设计问题。 你应该这样做,如果你收到了回报。

用途

  • 对于私人会员, Eric Lippert的回答描述了一个很好的用途 🙁 Memoization )。

  • 您可以使用它以基于函数的方式实现策略模式 ,而不是需要类层次结构。 我再次使用私人会员……

……示例代码:

 public class Context { private Func executeStrategy; public Context(Func executeStrategy) { this.executeStrategy = executeStrategy; } public int ExecuteStrategy(int a, int b) { return executeStrategy(a, b); } } 
  • 我发现了一个特殊情况,我认为公共委托属性是warrented:使用实例而不是派生类实现模板方法模式 …

…这在自动集成测试中特别有用,在这些测试中,您需要进行大量设置/拆卸。 在这种情况下,将状态保持在设计用于封装模式的类中而不是依赖于unit testing夹具通常是有意义的。 通过这种方式,您可以轻松支持在灯具之间共享测试套件的骨架,而无需依赖(有时是伪劣的)测试夹具inheritance。 它也可能更适合并行化,具体取决于测试的实现。

 var test = new MyFancyUITest { // I usually name these things in a more test specific manner... Setup = () => { /* ... */ }, TearDown = () => { /* ... */ }, }; test.Execute(); 

智能感知支持

该函数的外部用户可以看到未命名的参数 – 有时这是有用的,尽管能够命名它们会很好。

使用一个命名的委托 – 我相信这会让你至少得到一些参数的Intellisense(可能只是名字,不太可能的XML文档 – 请纠正我,如果我错了):

 public class MyClass { public delegate int DoSomethingImpl(int foo, int bizBar); public DoSomethingImpl DoSomething = (x, y) => { return x + y; } } 

我会避免委托属性/字段作为公共方法的方法替换。 对于私有方法,它是一种工具,但不是我经常使用的工具。

  • 实例委托字段具有每个实例的内存开销。 可能是大多数课程的过早优化,但仍需记住。

  • 您的代码使用公共可变字段,可以随时更改。 这伤害了封装。

  • 如果使用字段初始值设定项语法,则无法访问this语法。 因此,字段初始化程序语法主要用于静态方法。

  • 使静态分析更加困难,因为在编译时不知道该方法的实现。

在某些情况下,委托属性/字段可能有用:

  • 某种处理程序。 特别是如果多播(因而事件订阅模式)没有多大意义
  • 分配一些简单的方法体无法轻易描述的东西。 比如一个记忆function。
  • 委托是运行时生成的,或者至少它的值仅在运行时决定

对局部变量使用闭包是使用方法和私有字段的替代方法。 我非常不喜欢有很多字段的类,特别是如果这些字段中的一些只使用两种或更少的方法。 在这些情况下,在字段中使用委托可能优于传统方法

 class MyClassConventional { int? someValue; // When Mark() is called, remember the value so that we can do something with it in Process(). Not used in any other method. int X; void Mark() { someValue = X; } void Process() { // Do something with someValue.Value } } class MyClassClosure { int X; Action Process = null; void Mark() { int someValue = X; Process = () => { // Do something with someValue }; } } 

这个问题提出了一个错误的二分法 – 在函数之间,以及一个具有等效签名的委托。 主要区别在于,如果没有其他选择,您应该只使用其中一个。 在日常工作中使用它,它将被排除在任何代码审查之外。

所提到的好处远远超过这样一个事实,即几乎没有理由编写如此模糊的代码; 特别是当这段代码看起来你不知道如何编程C#时。

我敦促任何阅读此内容的人忽略已经陈述的任何好处,因为这些都是由于这种代码certificate你不知道如何使用C#进行编程这一事实而不堪重负。

该规则的唯一例外是,如果您需要其中一项好处,并且无法以任何其他方式满足该需求。 在这种情况下,您需要编写更多注释而不是代码来解释为什么您有充分理由这样做。 准备像Eric Lippert那样清楚地回答。 您最好能够解释,并且Eric确实无法满足您的要求并同时编写可理解的代码。