为什么C#不允许像C ++这样的非成员函数
C#将不允许编写非成员函数,每个方法都应该是类的一部分。 我认为这是所有CLI语言的限制。 但是我错了,我发现C ++ / CLI支持非成员函数。 编译时,编译器会使该方法成为某个未命名类的成员。
这是C ++ / CLI标准所说的,
[注:非成员函数被CLI视为某些未命名类的成员; 但是,在C ++ / CLI源代码中,这样的函数不能用该类名显式限定。 尾注]
元数据中非成员函数的编码是未指定的。 [注意:这不会引起互操作问题,因为这些function不能公开显示。 尾注]
所以我的问题是为什么不C#实现这样的东西? 或者你认为不应该有非会员职能,每种方法都应该属于某一类?
我的意见是有非会员function支持,这有助于避免污染类的界面。
有什么想法吗..?
看到这篇博文:
http://blogs.msdn.com/ericlippert/archive/2009/06/22/why-doesn-tc-implement-top-level-methods.aspx
和这个后续发布:
http://blogs.msdn.com/ericlippert/archive/2009/06/24/it-already-is-a-scripting-language.aspx
C#不允许它,因为Java不允许它。
我可以想到Java的devise者为什么不允许它的几个原因
- Java的devise很简单。 他们试图做一种没有任何快捷键的语言,所以通常只有一种简单的方法来做所有事情,即使其他方法更简洁也更简洁。 他们想要最小化学习曲线,而学习“一个类可能包含方法”比“一个类可能包含方法,function可能存在于类之外”更简单。
- 从表面上看,它看起来不太面向对象。 (任何不属于对象的东西显然不可能是面向对象的吗?当然,C ++是肯定的,但C ++并没有参与这个决定)
正如我在评论中已经说过的,我认为这是一个很好的问题,有很多情况下,非成员函数会更好。 (这部分主要是回答所有其他答案,说:“你不需要它”)
在C ++中,非成员函数是允许的,他们通常是首选的,原因如下:
- 它有助于封装。 访问类的私有成员的方法越less,类就越容易重构或维护。 封装是OOP的重要组成部分。
- 当代码不是一个类的一部分时,代码可以更容易被重用。 例如,C ++标准库将
std::find
或std :: sort定义为非成员函数,以便它们可以在任何types的序列上重用,无论是数组,集合,链表还是(对于std: :至lessfind)stream。 代码重用也是面向对象的重要组成部分。 - 它给了我们更好的解耦。
find
函数不需要知道LinkedList类为了能够在它上面工作。 如果它被定义为一个成员函数,它将是LinkedList类的成员,基本上将这两个概念合并成一个大块。 - 可扩展性。 如果您接受某个类的接口不仅仅是“其所有公共成员”,而且还包括“对类进行操作的所有非成员函数”,那么就可以扩展一个类的接口而不必编辑或甚至重新编译类本身。
具有非成员函数的能力可能源自于C(在这里你没有别的select),但是在现代C ++中,它本身是一个重要特征,不仅仅是为了向后兼容,而是因为更简单,它允许更清洁和更可重用的代码。
事实上,C#似乎已经实现了很多相同的事情,很久以后。 你为什么认为扩展方法被添加? 他们试图达到上述目的,同时保留简单的类似Java的语法。 羔羊也是一个有趣的例子,因为它们本质上也是自由定义的小函数,而不是任何特定类的成员。 所以是的,非成员函数的概念是有用的,C#的devise师已经实现了同样的事情。 他们只是试图通过后门偷偷摸摸的概念。
http://www.ddj.com/cpp/184401197和http://www.gotw.ca/publications/mill02.htm是由C ++专家撰写的关于这个主题的两篇文章。
非成员函数是一件好事, 因为它们改进了封装并减less了types之间的耦合。 大多数现代编程语言如Haskell和F#都支持自由函数。
没有把每个方法放在一个命名类中有什么好处? 为什么非成员函数会“污染”类的接口呢? 如果你不想把它作为一个类的公共API的一部分,或者不要公开它,或者不把它放在那个类中。 你总是可以创build一个不同的类。
我不记得曾经想写一个没有适当范围的方法 – 除了匿名函数(当然不是这样)。
总之,在非成员函数中我看不到任何好处,但是在将所有方法放在一个恰当命名的类中,我可以看到一致性,命名和文档方面的好处。
CLS(通用语言规范)说,你不应该在符合CLS的库中拥有非成员函数。 这就像一个额外的限制,除了CLI(公共语言接口)的基本限制。
未来的C#版本可能会添加写入using
指令的function,该指令允许在没有类名限定的情况下访问类的静态成员:
using System.Linq.Enumerable; // Enumerable is a static class ... IEnumerable<int> range = Range(1, 10); // finds Enumerable.Range
那么就不需要改变CLS和现有的库了。
这些博客文章演示了一个用C#进行函数式编程的库 ,它们使用一个只有一个字母长的类名,试图减less静态方法调用的要求所造成的噪音。 如果using
指令可以定位类,这样的例子会更好一点。
自Java以来,大多数程序员都轻易地接受任何方法都是类的成员。 我没有制造任何重大的障碍,使方法的概念更加狭隘,使语言更容易。
然而,事实上,类推断对象,对象推断状态,所以只包含静态方法的类的概念看起来有点荒谬。
- 将所有代码放在类中,可以提供更强大的reflectionfunction。
- 它允许使用静态初始化器,它可以初始化类中静态方法所需的数据。
- 它避免了方法之间的名称冲突,通过明确地将它们包含在一个不能被另一个编译单元添加的单元中。
我想你真的需要澄清一下你想创build非成员静态方法来实现。
例如,您可能需要的一些东西可以使用扩展方法来处理
另一个典型的使用(只包含静态方法的类)在库中。 在这种情况下,在完全由静态方法组成的程序集中创build类几乎没有什么坏处。 它使它们保持在一起,避免命名冲突。 毕竟,math中有静态的方法可以达到同样的目的。
另外,您不一定要将C ++的对象模型与C#进行比较。 C ++在很大程度上(但不是完全)与C兼容,C根本没有类系统 – 所以C ++必须支持C语言的编程习惯,而不是为了任何特定的devise要求。
记住一点:C ++是比C#复杂得多的语言。 尽pipe它们在语法上可能相似,但它们在语义上却是非常不同的。 你不会认为做这样的改变是非常困难的,但我可以看到它是如何的。 ANTLR有一个很好的wiki页面,叫什么使语言问题变得困难? 这是很好的咨询这样的问题。 在这种情况下:
上下文敏感的词法分析器? 除非你知道你正在parsing什么样的句子,否则你不能决定匹配什么词汇符号。
现在,不用担心类中定义的函数,我们不得不担心在类之外定义的函数。 从概念上讲,没有太大的区别。 但是从代码的分析和parsing来看,现在还有一个额外的问题,就是如果一个函数在类之外,它就属于这个未命名的类,但是如果它在类中,那么它属于类。”
另外,如果编译器遇到这样的方法:
public void Foo() { Bar(); }
…现在必须回答这个问题:“这个酒吧位于这个class级还是全球class级?”
转发还是外部引用? 也就是说,需要多个通行证? Pascal具有处理文件内过程引用的“前向”引用,但是通过USES子句等引用其他文件中的过程需要特殊的处理。
这是导致问题的另一件事。 请记住,C#不需要前向声明。 编译器将只进行一次传递,以确定哪些类被命名以及这些类包含哪些函数。 现在你不得不担心find类和函数,这些函数可以是类内部的,也可以是外部的。 这是一个C ++parsing器不需要担心的问题,因为它会按顺序parsing所有内容。
现在不要误解我的意思,这可能是用C#完成的,我可能会使用这样的特性。 但是当你只需要在一个静态方法的前面input一个类名时,是否真的值得克服这些障碍的麻烦?
如果你考虑一下,反驳的观点是“为什么C ++不支持扩展方法” ,答案在于他们的devise目标。
C ++为您提供名称空间函数,您可以在任何对象上调用这些函数,以帮助您以某种方式“扩展”该对象。
我在C ++中使用名称空间函数的大部分时间是创build接受对象的函数,并且执行我不想放入类成员函数的function。
在C#中,您可以创build一个扩展方法来为您完成工作(大部分时间)。 对于剩下的情况,您将错过该function。
以下面的代码为例:
template<typename T> unsigned int findCount(vector<T>& vec, T data){ unsigned int count = 0; for(auto val : vec) if(val == data) ++count; return count; }
现在,如果我在我的类中需要某种function,我可以将其添加到类的名称空间,并使用它,而不会使用该函数“污染”我的类。
在C#中,可以使用扩展方法实现相同的目标:
static class Extensions { public static uint FindCount<T>(this List<T> list, T data) { uint counts = 0; foreach (var item in list) if (item.Equals(data)) ++counts; return counts; } }
而且他们两人的工作方式都很酷。 许多人会争论C ++缺less扩展方法,许多人会争论C#缺less名称空间函数。
我认为最好的方式是把这些语言与一些细节进行比较,因为它们是具有不同实现的不同语言。
正如你在你的问题中引用的那样,C ++ / CLI并没有“真正”支持名称空间成员函数,因为它添加了一个未命名的类来使用它们,它们绝不接近于C ++的实现,可能closures的方式看起来非常不同。
最后,function总是令人期待的,尽pipe我希望C#中的命名空间成员函数与C ++中的扩展方法一样多。 反正没什么。