为什么 C # 不允许像 C + + 这样的非成员函数

C # 不允许编写非成员函数,每个方法都应该是类的一部分。我认为这是对所有 CLI 语言的限制。但是我错了,我发现 C + +/CLI 支持非成员函数。编译时,编译器将使该方法成为某个未命名类的成员。

这是 C + +/CLI 标准说的,

[注意: CLI 将非成员函数视为某个未命名类的成员; 但是,在 C + +/CLI 源代码中,这些函数不能用该类名显式限定。尾注]

未指定元数据中非成员函数的编码。[注意: 这不会导致互操作问题,因为这些函数不能具有公共可见性。尾注]

所以我的问题是,为什么 C # 不实现这样的东西呢?或者您认为不应该有非成员函数,每个方法都应该属于某个类?

我的观点是支持非成员函数,这有助于避免污染类的接口。

有什么想法吗?

26715 次浏览

没有将每个方法放在一个命名类中的好处是什么?为什么非成员函数会“污染”类的接口?如果您不希望它作为类的公共 API 的一部分,那么要么不将它公开,要么不将它放在那个类中。您总是可以创建不同的类。

我不记得我曾经想过要编写一个没有适当作用域的方法——当然,除了匿名函数之外(它们实际上并不相同)。

简而言之,我看不出非成员函数有什么好处,但是我可以从一致性、命名和文档方面看出将所有方法放在一个适当命名的类中的好处。

CLS (公共语言规范)规定,在符合 CLS 的库中不应该有非成员函数。它就像是除了 CLI (公共语言接口)的基本限制之外的一组额外限制。

C # 的未来版本可能会增加编写 using指令的能力,该指令允许在没有类名限定的情况下访问类的静态成员:

using System.Linq.Enumerable; // Enumerable is a static class


...


IEnumerable<int> range = Range(1, 10); // finds Enumerable.Range

这样就不需要更改 CLS 和现有库了。

这些博客文章展示了一个用于 C # 函数式编程的库 ,它们使用一个只有一个字母长的类名,试图减少限定静态方法调用的要求所造成的干扰。如果 using指令可以针对类,那么这样的示例会更好一些。

我认为您真的需要澄清您想要创建非成员静态方法来实现什么。

例如,可以使用 推广方法处理您可能需要它们的某些事情

(只包含静态方法的类的)另一个典型用法是在库中。在这种情况下,在完全由静态方法组成的程序集中创建类没有什么坏处。它将它们保持在一起,避免命名冲突。毕竟,在数学中有许多静态方法可以达到同样的目的。

此外,您不必将 C + + 的对象模型与 C # 进行比较。C + + 基本上(但不是完全)与 c 兼容,因为 c 根本没有类系统——所以 c + + 不得不支持 C 遗留下来的这个惯用法,而不是为了任何特定的设计要求。

自 Java 以来,大多数程序员都很容易接受任何方法都是一个类的成员。我没有制造任何可观的障碍,也没有使方法的概念更加狭窄,这使得语言更加容易。

然而,实际上,类推断对象,对象推断状态,所以只包含静态方法的类的概念看起来有点荒谬。

非成员函数是 因为它们改善了封装的优点,可以减少类型之间的耦合。Haskell 和 F # 等大多数现代编程语言都支持免费函数。

C # 不允许,因为 Java 不允许。

我可以想到几个 Java 设计者不允许它的原因

  • Java 被设计得很简单。他们试图创建一种没有随机快捷方式的语言,这样你通常只有一种简单的方法来处理所有事情,即使其他方法可能会更简洁或者更简洁。他们希望最小化学习曲线,学习“类可能包含方法”比“类可能包含方法,函数可能存在于类之外”更简单。
  • 从表面上看,它不太面向对象。(任何不是对象一部分的东西显然不能是面向对象的?可以吗?当然,C + + 说是的,但是 C + + 并没有参与这个决定)

正如我在评论中已经说过的,我认为这是一个好问题,在很多情况下,非成员函数更可取。(这一部分主要是对所有其他答案的回答: “你不需要它”)

在 C + + 中,允许使用非成员函数,因为以下几个原因,非成员函数通常更受欢迎:

  • 有助于封装。访问类的私有成员的方法越少,类就越容易重构或维护。封装是 OOP 的重要组成部分。
  • 当代码不是类的一部分时,可以更容易地重用它。例如,C++标准程式库将 std::find或 std: : sort‘定义为非成员函数,以便它们可以在任何类型的序列上重用,无论是数组、集合、链表还是(至少对于 std: : find)流。代码重用也是 OOP 的一个重要组成部分。
  • 它让我们更好地脱钩。find函数不需要了解 LinkedList 类就能够处理它。如果它被定义为一个成员函数,那么它就是 LinkedList 类的一个成员,基本上就是将两个概念合并成一个大块。
  • 可扩展性。如果你承认一个类的接口不仅仅是“所有的公共成员”,而且是“所有在类上运行的非成员函数”,那么就有可能扩展一个类的接口,而不必编辑甚至重新编译这个类本身。

拥有非成员函数的能力可能起源于 C (在这里你别无选择) ,但是在现代 C + + 中,它本身就是一个重要的特性,不仅仅是为了向后可比性的目的,而且因为它允许更简单、更清晰和更可重用的代码。

事实上,C # 在很久以后似乎也意识到了同样的事情。您认为为什么要添加扩展方法?它们是实现上述目标的一种尝试,同时保留了简单的类 Java 语法。 Lambdas 也是有趣的例子,因为它们本质上也是自由定义的小函数,不是任何特定类的成员。因此,非成员函数的概念是有用的,C # 的设计者也意识到了同样的事情。他们只是想从后门把这个概念偷偷带进来。

Http://www.ddj.com/cpp/184401197 http://www.gotw.ca/publications/mill02.htm是两篇由 C + + 专家撰写的文章。

请记住: C + + 是一种比 C # 复杂得多的语言。虽然它们在句法上可能相似,但在语义上却完全不同。你不会认为做出这样的改变是非常困难的,但我可以看到它是如何做到的。ANTLR 有一个很好的维基页面,叫做 什么使语言问题变得困难?,可以很好地为这样的问题提供咨询。在这种情况下:

上下文敏感词典?你不能决定哪些词汇符号匹配,除非你知道你在解析什么样的句子。

现在,我们不仅要担心在类中定义的函数,还要担心在类外定义的函数。从概念上讲,没有太大区别。但是在对代码进行词法分析和解析方面,现在又多了一个问题: 必须说“如果一个函数位于类之外,那么它就属于这个未命名的类。然而,如果它在这个类别内,那么它就属于这个类别。”

另外,如果编译器遇到这样的方法:

public void Foo()
{
Bar();
}

它现在必须回答这个问题“ Bar 是属于这个类,还是一个全球类?”

向前还是向外引用?也就是说,需要多次通行证?Pascal 有一个“转发”引用来处理文件内过程引用,但是通过 USES 子句等引用其他文件中的过程。.需要特殊处理。

这是另一件会引起问题的事情。请记住,C # 不需要前向声明。编译器只会一次性确定哪些类被命名,以及这些类包含哪些函数。现在您必须考虑如何找到类 还有函数,其中函数可以位于类的内部或外部。这是一个 C + + 语法分析器不必担心的问题,因为它会按顺序解析所有内容。

现在不要误解我的意思,它可以在 C # 中完成,我可能会使用这样的特性。但是,当您只需要在静态方法前面键入一个类名时,克服这些障碍真的值得吗?

  • 将所有代码都放在类中可以实现更强大的反射功能集。
  • 它允许使用静态初始化器,它可以初始化类中静态方法所需的数据。
  • 它通过将方法显式封装在另一个编译单元无法添加的单元中,避免了方法之间的名称冲突。

看看这篇博文:

Http://blogs.msdn.com/ericlippert/archive/2009/06/22/why-doesn-t-c-implement-top-level-methods.aspx

(...)

有人问我“为什么 C # 不实现特性 X?”一直都是。答案总是相同的: 因为从来没有人设计、指定、实现、测试、记录和发布过这个特性。所有这六件事对于一个特性的实现都是必要的。所有这些都需要花费大量的时间、精力和金钱。功能并不便宜,而且我们非常努力地确保我们只提供那些在我们有限的时间、精力和资金预算下能给我们的用户带来最大利益的功能。

我明白,这样一个笼统的答案可能并没有解决具体的问题。

在这种特殊情况下,明确的用户利益在过去不足以证明语言的复杂性。通过限制不同的语言实体如何相互嵌套,我们(1)限制合法程序采用通用的、易于理解的风格,(2)使定义“标识符查找”规则成为可能,这些规则是可理解的、可指定的、可实现的、可测试的和可文档化的。

通过限制方法主体总是位于结构或类中,我们可以更容易地推断调用上下文中使用的非限定标识符的含义; 这样的东西总是当前类型(或基类型)的可调用成员。

(...)

以及后续的帖子:

Http://blogs.msdn.com/ericlippert/archive/2009/06/24/it-already-is-a-scripting-language.aspx

(...)

像所有的设计决策一样,当我们面对许多相互竞争的、引人注目的、有价值的和不可组合的想法时,我们必须找到一个可行的折衷方案。我们不这样做,除了 考虑到所有的可能性,这就是我们在这种情况下正在做的。

(强调来自原文)

如果将免费函数与 Duck 类型结合使用,它们非常有用。整个 C + + STL 都是以它为基础的。因此,我确信 C # 将引入免费的函数,当他们设法添加真正的泛型。

与经济学一样,语言设计也与心理学有关。如果您通过 C # 中的自由函数创建了对真正的泛型的需求,但没有交付,那么您将扼杀 C # 。然后所有的 C # 开发人员都会迁移到 C + + ,没有人希望这种情况发生,C # 社区不希望这样,当然那些投资 C + + 的人也不希望这样。

虽然确实需要一个类(例如一个名为 FreeFunctions的静态类)来保存这些函数,但是您可以将 using static FreeFunctions;放在任何需要它的函数的文件的顶部,而不必使用 FreeFunctions.限定符来破坏代码。 我不确定是否存在这样的情况,即这明显不如不要求函数定义包含在类中。

其他编程语言很难从编译器的角度定义函数实例的内部特性。在 Pascal 和 C 中,实例基本上定义为 只能作为指针处理的。尤其是,9个计算机科学教授中有7个是坚决反对读写可执行代码的。作为类的成员,没有人需要关心如何处理其表现形式,因为该表现形式的类型是从类属性派生的。可以创建一个像全局函数一样处理的函数: 一个 lambda 函数,分配给一个变量:

Func<int,int> myFunc = delegate(int var1)
{
Console.WriteLine("{0}",var1*2);
return var1*3;
};
。它可以像全局函数一样简单地通过它的变量名来调用。 如果是这样,那么区别就在于在最低级别实现了一个新的对象类型,其行为与另一个对象类型相同。这被有经验的程序员认为是不好的做法,可能因此而被废弃。

CSharp 没有非成员函数,因为它复制或受到了 Java 哲学的启发,即只有面向对象方法才是所有问题的解决方案,它只允许用面向对象方法解决问题。

非成员函数是非常重要的功能,如果我们真的想做泛型。与将它们放在类中相比,它们更具可重用性。

由于缺少非成员函数,CSharp 必须提出 ExtensionMethod。

目前,编程语言正朝着函数编程范型的方向发展,这似乎是解决问题的更好方法,也是未来的趋势。夏普应该重新考虑一下。