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


68

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

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

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

未指定元数据中非成员函数的编码。[注意:这不会引起互操作问题,因为此类功能无法公开显示。尾注]

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

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

有什么想法吗..?


1
F#还支持没有显式类的方法(然后从文件名为隐式类创建一个名称)。
理查德

Answers:


88

请参阅此博客文章:

http://blogs.msdn.com/ericlippert/archive/2009/06/22/why-doesn-tc-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

(...)

像所有设计决策一样,当我们面对许多相互竞争,令人信服,有价值和不可行的想法时,我们必须找到可行的折衷方案。除了考虑所有可能性外,我们不会这样做,这是我们在本例中所做的。

(强调原文)


4
很好,但这应该是(并且是)评论,而不是答案。
Beska

7
@Beska,因为它显然是对原始问题的答案(并且是唯一有出处的答案!),您如何看待这一点?
Daniel Earwicker 09年

54
我不仅发布“我尊重的博客作者的链接”,而且还发布自己的博客的链接。
埃里克·利珀特

2
也许他正在努力赢得自己的尊重。也许是通过服从一系列痛苦的考验。如来访SO!:)
Daniel Earwicker 2009年

2
@Eric:我碰巧知道一个事实,你喜欢它,当我们推测一下你的精神状态和目前作为事实。
配置器

39

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

我可以想到Java设计师可能不允许这样做的几个原因

  • Java的设计很简单。他们尝试制作一种没有随机快捷方式的语言,因此,即使其他方法更简洁或更简洁,您通常也只有一种简单的方法可以完成所有工作。他们希望最小化学习曲线,并且学习“一个类可能包含方法”比“一个类可能包含方法并且函数可能存在于类外部”要简单得多。
  • 从表面上看,它看起来不太面向对象。(显然,不是对象的任何部分都不能面向对象吗?可以吗?C ++可以,但是C ++并未参与该决定)

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

在C ++中,允许使用非成员函数,由于以下几个原因,它们通常是首选的:

  • 它有助于封装。方法可以访问的类的私有成员越少,该类将更易于重构或维护。封装是OOP的重要组成部分。
  • 如果代码不是类的一部分,则可以更轻松地重用代码。例如,C ++标准库将std::findstd :: sort或std :: sort定义为非成员函数,因此它们可以在任何类型的序列上重用,无论是数组,集合,链接列表还是(对于std :: find,位于最少)流。代码重用也是OOP的重要组成部分。
  • 它使我们更好地去耦。该find函数不需要知道LinkedList类就可以对其进行处理。如果已将其定义为成员函数,则它将是LinkedList类的成员,基本上将这两个概念合并为一个大对象。
  • 可扩展性。如果您接受一个类的接口不仅是“其所有公共成员”,而且是“对该类进行操作的所有非成员函数”,那么可以扩展类的接口而无需进行编辑或修改。甚至重新编译类本身。

具有非成员函数的能力可能起源于C(您别无选择),但是在现代C ++中,它本身就是一项至关重要的功能,不仅是为了向后兼容,而且因为更简单允许的更清晰,更可重用的代码。

实际上,C#似乎在很晚以后就实现了很多相同的事情。您为什么认为添加了扩展方法?它们是在实现上述目的的同时保留简单的类似于Java的语法的尝试。Lambda也是有趣的示例,因为它们本质上也是自由定义的小函数,而不是作为任何特定类的成员。因此,是的,非成员函数的概念很有用,C#的设计师也意识到了同样的事情。他们只是试图通过后门潜入这个概念。

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


57
“ C#不允许,因为Java不允许。” -听到您如何得出这个特定结论,我会很着迷。你那天参加设计会议吗?您已阅读设计团队的说明吗?一位设计师告诉过你吗?
Eric Lippert

8
@埃里克(Eric):上个世纪末,微软处于复制模式,至少是公开的。有了J#之类的东西以及整个Sun / Microsoft行,就无法否认Java影响了C#和CLI。它并不自动地适用于此特定(错误)功能。但是,值得注意的是,通用语言基础结构中缺少这种通用语言功能。
MSalters

31
当然,Java对C#产生了影响。就像C,C ++,JScript,Visual Basic,Pascal和许多其他语言一样。但是C#并非像许多人认为的那样是“去除了愚蠢部分的Java”。
埃里克·利珀特

30
绝对。C#和Java分享了许多令人困惑或容易出错的习惯用法。我们试图从Java设计的缺点中吸取教训。在很多地方,C#编译器会针对惯用语生成警告或错误,在这些地方等效的Java代码是合法的但具有误导性。但是,我们绝不可能接近所有这些人。
Eric Lippert

14
@Eric:不,我没有参加那次会议。:)但是,鉴于C#从Java中获得了很多启发(正如您所说的),并且C ++确实允许使用非成员函数,并且这样做有很多很好的理由(不仅仅是“我们需要与C兼容”)。 ”),除了“嘿,它在Java中起作用,让我们坚持下去”这一主题之外,我真的没有想到任何其他原因,我认为这是最可能的解释。不管这个决定背后的原因是什么,如果Java没有首先做到这一点,您甚至会考虑它吗?
jalf

12

非成员函数是一件好事,因为它们改善了封装并减少了类型之间的耦合。大多数现代编程语言(例如Haskell和F#)都支持自由功能。


6
是的 我所有的新项目都在C#中,我真的很想念自由功能功能:(
Navaneeth KN09年

4
扩展方法有很大帮助,但我同意,适当的自由功能将是一个不错的选择
jalf

1
您仍然可以使用静态类来达到相同的效果。
贾森·贝克

14
“静态类”没有实际目的。它们就像名称空间一样(除非它们很难键入)-为什么不使用真实的名称空间呢?
Nemanja Trifunovic

9

将每个方法放在命名类中的好处是什么?为什么非成员函数会“污染”类的接口?如果您不希望将其作为类的公共API的一部分,请不要使其公开或不将其放入该类中。您始终可以创建其他类。

我不记得曾经想编写一个没有适当范围的浮动方法-当然,除了匿名函数(实际上并不太一样)之外。

简而言之,我看不到非成员函数的任何好处,但是我看到了将所有方法放入适当命名的类中的一致性,命名和文档方面的好处。


44
在C ++中,通常会接受非成员函数是首选。逻辑很简单。访问类私有资源的方法越少越好。封装得越好,维护起来就越容易。当然,它允许在类之间进行更多的代码重用。(例如,标准库std :: find是可用于任何迭代器类型的单个实现。在C#中,每个容器类都必须定义自己的find函数。哪个OOP最多?哪个提供最多的代码可重用性? ;)
jalf

10
对于大多数Java / C#程序员来说,这有点让人感到文化震惊,但是背后的逻辑非常简单。例如,请参见ddj.com/cpp/184401197。但是,我认为您回答得很好。C#不允许这样做,因为对于非C ++程序员来说,它看起来很奇怪,令人恐惧且非OOP。;)它与许多“常规” OOP语言所教的内容背道而驰。
jalf

10
@Jon:但是为什么要把它放在静态类中呢?与将其放置在所有班级之外,这给我带来什么好处?从概念上讲,它不属于该其他静态类。但是,匿名类不是创建它们的类的成员。它们被实现为很多技巧,并创建了一个新的匿名类。至于类外的全局变量,不可以。全局变量不能像自由函数那样有意义地视为类接口的一部分。为什么您认为静态类比名称空间更好的分组构造?
jalf

8
“为什么您认为静态类比名称空间更好的分组构造?” 借调!:)如果允许使用非成员函数(以及所有其他相等的函数),则命名空间将优于静态类,因为它可以被using定位。或等效地,我们可以问:为什么“ using”指令不能以静态类为目标,从而允许我们无条件调用其方法?
丹尼尔·艾威克

10
静态类如何成为比名称空间小的单元?例如,包含3个非成员函数的名称空间是否比包含3个静态成员函数的静态类更大的单元?除了这三种方法外,该类还包含许多其他基础结构(例如,类型id)。这是否会使静态类比等效的名称空间成为更大的单元?
jalf

3

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指令可以以类为目标,则这样的示例会更好一些。


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

2

从Java开始,大多数程序员已经很容易接受任何方法都是类的成员。我不会遇到任何障碍,也不会使方法的概念更狭窄,这使语言变得更容易。

但是,实际上,类推断对象,而对象推断状态,因此仅包含静态方法的类的概念看起来有点荒谬。


1
仅当类是实例方法时,类才推断对象。对于仅包含静态方法的类的概念,我认为没有什么荒谬的-这是一种非常有用的模式,它在C#2中得到了适当的语法支持:)这样说吧-我宁愿拥有类“ IOUtil”,“ StringUtil”等具有的所有实用程序方法都仅位于名称空间级别。
乔恩·斯基特

10
这很荒谬。类应该是用于创建对象的模式,而不是方法的容器。
Nemanja Trifunovic

4
我认为将静态方法放入类或名称空间也不是什么大不了的事情。但是考虑一下“阶级”这个词。类什么?我认为答案是对象,而不是您可以在程序中使用的东西;)。
德米特里·塔什基诺夫

1
当然有点奇怪。从逻辑上讲,静态类和包含(假设的)非成员函数的名称空间之间没有区别。坚持将所有实用程序方法都包含在静态类中并没有什么比坚持将所有小型实用程序类都包含在静态类中
更有意义

1

我认为您确实需要澄清要创建非成员静态方法来实现的目标。

例如,某些您可能希望使用的方法可以通过扩展方法来处理

(仅包含静态方法的类的)另一种典型用法是在库中。在这种情况下,在完全由静态方法组成的程序集中创建类的危害很小。它将它们保持在一起,避免命名冲突。毕竟,数学中有一些静态方法可以达到相同的目的。

另外,您不必将C ++的对象模型与C#进行比较。C ++在很大程度上(但不是完美地)与根本没有类系统的C兼容-因此C ++必须支持C语言中的这种编程习惯,而不是出于任何特定的设计要求。


1

注意事项:C ++是比C#更复杂的语言。尽管它们在语法上可能相似,但它们在语义上却是完全不同的野兽。您不会认为进行这样的更改会非常困难,但是我知道它会如何。ANTLR有一个很好的Wiki页面,称为“什么使语言问题难以解决?”。咨询像这样的问题很好。在这种情况下:

上下文敏感的词法分析器?您无法确定要匹配的vocabulay符号,除非您知道要解析的句子类型。

现在,我们不必担心类中定义的函数,而不必担心类外定义的函数。从概念上讲,没有太大区别。但是就词法分析和代码而言,现在还有一个额外的问题,就是必须说“如果函数在类外部,则它属于该未命名的类。但是,如果函数在类内部,则它属于该类。类。”

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

public void Foo()
{
    Bar();
}

...现在必须回答“ Bar位于此类中还是全局类?”这个问题。

转发还是外部参考?即,需要多次通过?Pascal有一个“转发”引用来处理文件内过程的引用,但是通过USES子句等引用其他文件中的过程需要特殊处理。

这是引起问题的另一件事。请记住,C#不需要前向声明。编译器将通过以确定是否命名了哪些类以及这些类包含的功能。现在,您不必担心在哪里可以在类的内部或外部找到类函数。这是C ++解析器不必担心的事情,因为它按顺序解析所有内容。

现在不要误会我的意思,它可以在C#中完成,而我可能会使用这种功能。但是,只要您可以在静态方法前面键入类名,是否真的值得克服这些障碍呢?


1
忘了C ++。F#或VB.NET怎么样?他们确实支持免费功能。
Nemanja Trifunovic

7
如果出于某种愚蠢的原因,您必须在C#中的每个标识符前面键入MONKEY,我们可以这样说:“必须在每个标识符前面键入MONKEY真的有那么大的事情吗?大惊小怪的打字!” 但这并不能阻止它成为一种奇怪且不必要的强加措施。要求对静态函数的所有引用都必须由类名进行限定,这是相同的。不需要它,浪费每个人的时间。
Daniel Earwicker,2009年

@Nemanja-它们仍然是非常不同的语言,具有不同的语法。看,我同意全局功能会有所帮助。但是在获得免费功能之前,我个人宁愿使用动态类型,协/逆方差,可选参数和命名参数。
杰森·贝克

1
实际上,在获得“免费”方法之前,您获得所有这些东西。:-)
Eric Lippert

1
@Jason-我不必太担心此讨论的结果会影响C#4.0的功能集!
Daniel Earwicker 09年

0

如果将自由功能与鸭子类型结合使用,则非常有用。整个C ++ STL都基于此。因此,我确信C#在设法添加真正的泛型时将引入自由函数。

像经济学一样,语言设计也与心理学有关。如果您通过C#中的自由函数对真正的泛型产生了胃口而没有交付,那么您将杀死C#。然后,所有C#开发人员都将转向C ++,没有人希望这种情况发生,而不是C#社区,而且最肯定不是那些投资C ++的人。


0

的确,您需要一个类(例如,称为的静态类FreeFunctions)来保存此类函数,但您可以将其随意放置using static FreeFunctions;在需要使用该函数的任何文件的顶部,而不必用FreeFunctions.限定符来填充代码。我不确定是否确实存在这种情况,这种情况明显不如不要求将函数定义包含在类中。


错误CS0138:“使用名称空间”指令只能应用于名称空间;
ZEE

关于“使用命名空间”指令,我什么也没说。
Dylan Nicholson
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.