【问题标题】:Delegates vs Interfaces in C#C# 中的委托与接口
【发布时间】:2012-01-31 11:15:05
【问题描述】:

只要我目前正在尝试深入研究代表的用途和目的,我就想提出这个问题,尽管很可能已经以类似的方式提出了这个问题。

我知道委托充当 C++ 中使用的函数指针。事实上,如果在 C# 中,它们主要用作接口和多态性的替代品。既然我可以创建特定类的子类并为它们提供适当的方法给每个子类,那么还有什么提供代表呢?是否有规定使用它们的案例,或者只是在使用委托时提高了代码的可维护性?你会推荐他们在接口上的广泛部署吗?

我只是在谈论代表,我想将他们的角色与事件角色区分开来。

【问题讨论】:

  • “事实上,如果在 C# 中,它们主要是接口和多态的替代品” - 请解释你的推理。
  • 这只是一个(间接的)问题。我认为它们的功能如我的问题中所述,我想知道您是否同意以及是否属实。

标签: c# interface delegates


【解决方案1】:

委托和接口是 C# 中两个不同的概念。

接口允许扩展某些对象的功能,它是接口和实现它的对象之间的契约,而委托只是安全的回调,它们是一种函数指针。

【讨论】:

    【解决方案2】:

    是的,委托在很多方面都类似于单方法接口。然而:

    • CLR 内置了对它们的支持
    • 框架支持它们,包括多播功能和异步调用
    • 以方法组转换、lambda 表达式、匿名方法的形式提供额外的 C#/VB 语言支持
    • 它们被强制用于事件(即事件和委托是一种匹配对)
    • 这意味着您不需要为要创建的每个委托实例在单独的类中实现接口。

    最后一点是最重要的——考虑一个 LINQ 表达式:

    var query = collection.Where(x => x > 5)
                          .Select(x => x * x);
    

    现在想象一下,如果要表达x > 5x * x 的逻辑,您必须为每个表达式编写一个单独的类,并实现一个接口:杂乱无章的代码与有用代码的数量是荒谬的。当然,现在语言可以被设计为允许通过单独的类从 lambda 表达式转换为接口实现,但是你仍然会失去能够简单地编写一个单独的方法并创建一个以它为目标委托。你仍然会失去多重施法能力。

    作为类似的思考练习,考虑循环语句,例如 whilefor。当我们有goto 时,我们真的需要吗?没有。但是有他们,生活会好很多。委托也是如此 - 实际上是属性、事件等。它们都使开发变得更简单。

    【讨论】:

    • 我不认为 Linq 示例显示了代表的优势;即使委托类型Func<int, bool> 被替换为接口IFunc<int, bool>,编译器也可以轻松定义一个实现该接口的类,并包含一个返回私有静态实例的公共静态属性。这种方法与不关闭任何局部变量的 lambda 表达式的委托大致相同,并且比关闭局部变量的 lambda 表达式的委托更有效。
    • @supercat:LINQ 示例显示了为此使用语法糖是多么有益。它可以用接口来完成,当然——但它不在 C# 中,在 Java 中还没有。请注意,对于封闭局部变量的 lambda 表达式,无论如何您都不能拥有单个实例,因为您每次都需要创建一个新实例来保存捕获的变量。
    • 我想我把原来的问题误读为理论多于实际;如果 .net 框架是围绕接口而不是委托设计的,编译器几乎肯定会为前者提供与后者相同类型的支持。至于闭包,目前创建闭包需要创建一个保存变量的对象和一个保存对该对象和所需函数的引用的委托;如果可以使用接口而不是委托,编译器就可以简单地创建一个对象来为这两种功能提供服务。
    • @supercat:确实如此,尽管我想知道这是否在内部以某种方式进行了优化。我在回答中提到了将 lambda 表达式转换为接口的可能性 - 但异步和多播性质还有其他好处,以及它们保证是单一方法的事实,而不仅仅是一个可以扩展的接口(然后不适合事件处理,例如,因为没有单一的方法可以调用)。
    • @supercat:当与方差一起使用时,组合委托确实会产生问题——但单动作委托可以正常工作。例如:` Action x = o => Console.WriteLine(o);动作 y = x; Console.WriteLine(x == y);` - 这将打印 True。 (不过它需要 C# 4 和 .NET 4。)
    【解决方案3】:

    实际最大的不同是你可以为来自同一个类的同一个委托提供不同的委托实例,而你不能用接口来做到这一点。

    delegate void XYZ(int p);
    
    interface IXyz {
        void doit(int p);
    }
    
    class One {
        // All four methods below can be used to implement the XYZ delegate
        void XYZ1(int p) {...}
        void XYZ2(int p) {...}
        void XYZ3(int p) {...}
        void XYZ4(int p) {...}
    }
    
    class Two : IXyz {
        public void doit(int p) {
            // Only this method could be used to call an implementation through an interface
        }
    }
    

    【讨论】:

      【解决方案4】:

      来自When to Use Delegates Instead of Interfaces (MSDN)

      委托和接口都使类设计器能够分离类型声明和实现。给定的接口可以由任何类或结构继承和实现。可以为任何类上的方法创建委托,只要该方法符合委托的方法签名即可。不知道实现接口或委托方法的类的对象可以使用接口引用或委托。鉴于这些相似之处,类设计者应该什么时候使用委托,什么时候应该使用接口?

      在以下情况下使用委托:

      • 使用事件设计模式。
      • 最好封装一个静态方法。
      • 调用者无需访问实现该方法的对象上的其他属性、方法或接口。
      • 需要简单的构图。
      • 一个类可能需要多个方法的实现。

      在以下情况下使用接口:

      • 有一组相关的方法可以调用。
      • 一个类只需要一个方法实现。
      • 使用该接口的类希望将该接口转换为其他接口或类类型。
      • 正在实现的方法与类的类型或标识相关联:例如,比较方法。

      使用单方法接口而不是委托的一个很好的例子是IComparable 或通用版本IComparable(Of T)。 IComparable 声明了 CompareTo 方法,该方法返回一个整数,指定相同类型的两个对象之间的小于、等于或大于关系。 IComparable 可以用作排序算法的基础。尽管使用委托比较方法作为排序算法的基础是有效的,但它并不理想。因为比较的能力属于类,并且比较算法在运行时不会改变,所以单方法接口是理想的。

      来自Delegates Vs Interfaces in C#

      委托和接口在 C# 中是两个不同的概念,但它们有一个共同点。委托和接口都只包含声明。实现由不同的编程对象完成。

      【讨论】:

      • 有用的答案;如果您或其他任何人可以添加您的第 2-5 个要点的示例,说明代表可以在哪些方面发挥作用,那也很棒。事件模式我认为不需要任何示例,因为它已被广泛讨论。
      【解决方案5】:

      委托优于接口的唯一真正优势是

      1. 甚至在 .Net 支持泛型之前,代表就可以处理不同的参数类型。
      2. 一个类可以创建委托,公开多个不同的方法共享相同的签名;用接口替换委托意味着每个类都可以公开一个实现每个委托样式接口的方法,而无需创建辅助类,但每个附加实现都需要一个辅助类。

      当 .net 不支持泛型时,委托是其中必不可少的一部分,因为为每个想要传递的不同函数签名声明不同的非泛型接口是不可行的。如果 .net 从一开始就支持泛型,那么除了涉及反射的某些场景外,委托就不是必需的,即使在这种情况下,将 Action<T,U> 类型作为 IAction<T,U> 的实现可能是最有用的(这样只需要它可以Invoke 的代码将使用该接口)。在类需要创建暴露多个方法的委托的情况下,基于接口的方法将需要创建单方法类,但在许多常见情况下不需要创建单独的委托实例,其中一个方法的数量要公开的给定签名恰好是一个。

      顺便说一句,用接口替换委托绝不会阻止创建通用的 Combine 方法。实际上,接口协方差可以使这种方法在许多方面都比现有的Delegate.Combine 工作得更好。实现类似于Delegate.Remove 的方法充其量是笨重和烦人的,但我想不出除了需要使用Delegate.Remove 的事件订阅管理之外的任何情况,并且无论如何最好使用其他方法来处理事件订阅。

      【讨论】:

        猜你喜欢
        • 2012-07-05
        • 2012-06-25
        • 1970-01-01
        • 1970-01-01
        • 2012-02-11
        • 2014-08-18
        • 2021-02-20
        • 2011-05-03
        • 2020-08-22
        相关资源
        最近更新 更多