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

时间:2009-06-21 15:39:59

标签: c# .net function c++-cli clr

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

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

  

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

     

未指定元数据中非成员函数的编码。 [注意:这不会导致互操作问题,因为此类功能无法获得公共可见性。结束说明]

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

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

有什么想法..?

14 个答案:

答案 0 :(得分:86)

请参阅此博文:

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

  

(...)

     

与所有设计决策一样,当我们遇到许多竞争,引人注目,有价值和不可能的想法时,我们必须找到可行的妥协方案。除非考虑所有可能性,否则我们不这样做,这就是我们在这种情况下所做的事情。

(重点来自原文)

答案 1 :(得分:38)

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

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

  • Java的设计很简单。他们试图制作一种没有随机快捷方式的语言,因此你通常只有一种简单的方法可以做所有事情,即使其他方法更简洁也更简洁。他们希望尽量减少学习曲线,学习“一个类可能包含方法”比“类可能包含方法,函数可能存在于类外”更简单。
  • 从表面上看,它看起来不那么面向对象。 (任何不属于对象的东西显然不能面向对象?可以吗?当然,C ++说是的,但是C ++没有参与这个决定)

正如我在评论中已经说过的那样,我认为这是一个很好的问题,而且很多情况下非会员职能会更受欢迎。 (这部分主要是对所有其他答案的回答,说“你不需要它”)

在C ++中,允许使用非成员函数,它们通常是首选,原因如下:

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

拥有非成员函数的能力可能源于C(你别无选择),但在现代C ++中,它本身就是一个至关重要的特性,不仅仅是为了向后兼容,而是因为它允许的更简单,更简洁,更可重用的代码。

事实上,C#似乎已经实现了很多相同的东西,很久以后。您为什么认为添加了扩展方法?它们是实现上述目标的尝试,同时保留了简单的类似Java的语法。 Lambdas也是有趣的例子,因为它们本质上是自由定义的小函数,而不是任何特定类的成员。所以是的,非成员函数的概念是有用的,C#的设计者也意识到了同样的事情。他们只是试图通过后门偷偷摸摸这个概念。

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

答案 2 :(得分:11)

非成员函数是一件好事because they improve encapsulation并减少了类型之间的耦合。大多数现代编程语言(如Haskell和F#)都支持自由函数。

答案 3 :(得分:9)

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

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

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

答案 4 :(得分: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和现有的库。

These blog posts demonstrate a library用于C#中的函数式编程,并且它们使用只有一个字母长的类名,以尝试减少由限定静态方法调用的要求引起的噪声。如果using指令可以定位类,那么这样的例子会更好一些。

答案 5 :(得分:3)

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

答案 6 :(得分:2)

自Java以来​​,大多数程序员都很容易接受任何方法都是类的成员。我没有做出任何相当大的障碍,并且使方法的概念更加狭窄,这使得语言更容易。

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

答案 7 :(得分:1)

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

例如,您可能需要的一些内容可以使用Extension Methods

进行处理

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

此外,您不一定要将C ++的对象模型与C#进行比较。 C ++在很大程度上(但并不完美)与C兼容,C根本没有类系统 - 因此C ++必须支持C遗留下的编程习惯,而不是任何特定的设计命令。

答案 8 :(得分:1)

牢记一点:C ++是一种比C#复杂得多的语言。虽然它们可能在语法上类似,但它们在语义上是非常不同的野兽。你不会认为做这样的改变会非常困难,但我可以看出它是怎么回事。 ANTLR有一个很好的wiki页面,名为What makes a language problem hard?,很适合咨询这样的问题。在这种情况下:

  

上下文敏感的词法分析器?除非您知道要解析的是哪种句子,否则无法确定要匹配的词汇符号。

现在我们不必担心在类中定义的函数,而是担心在类外定义的函数。从概念上讲,没有太大区别。但就lexing和解析代码而言,现在你有一个额外的问题,就是不得不说“如果一个函数在一个类之外,它属于这个未命名的类。但是,如果它在类中,那么它属于那个类“。

此外,如果编译器遇到如下方法:

public void Foo()
{
    Bar();
}

......它现在必须回答这个问题“Bar是否位于这个类中,还是全球类?”

  

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

这是导致问题的另一件事。请记住,C#不需要前向声明。编译器只进行一次传递以确定命名的类以及这些类包含的函数。现在你必须担心找到类函数,其中函数可以在类的内部或外部。这是C ++解析器不必担心的事情,因为它按顺序解析所有内容。

现在不要误解我的意思,它可能在C#中完成,我可能会使用这样的功能。但是当你在静态方法前面输入一个类名时,是否真的值得克服这些障碍呢?

答案 9 :(得分:0)

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

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

答案 10 :(得分:0)

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

答案 11 :(得分:0)

看,其他编程语言很难从编译器的角度定义函数实例的内部性质。在 Pascal 和 C 中,实例基本上被定义为只能作为指针处理的东西。特别是,由于读/写可执行代码位置是 9 位计算机科学教授中有 7 位死心塌地反对的。作为一个类的成员,没有人需要关心如何对待它的表现,因为这个表现的类型是从一个类属性派生的。可以创建一些完全像全局函数一样处理的东西:一个 lambda 函数,分配给一个变量:

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

答案 12 :(得分:-1)

Csharp 没有非成员函数,因为它复制或启发了 Java 的哲学,即只有 OOP 才能解决所有问题,并且只允许使用 OO 方式解决问题。

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

由于缺少非成员函数,CSharp 不得不提出 ExtensionMethods。

现在编程语言正朝着函数式编程范式发展,它似乎是解决问题的更好方法,也是未来。 CSharp 应该重新考虑一下。

答案 13 :(得分:-2)

如果你仔细想想,反驳的论点就是&#34;为什么C ++不支持扩展方法&#34; ,答案就在于他们的设计目标。

C ++为您提供了可以在任何对象上调用的命名空间函数,这些函数可以帮助您扩展&#34;那个对象在某种程度上。

我在C ++中使用命名空间函数的大部分时间都是创建接受对象的函数,并执行我不希望放入类成员函数的功能。

在C#中,您可以创建一个为您完成工作的扩展方法(大多数时候)。对于案例的左侧,您将错过该功能。

以下面的代码为例:

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;
}

现在,如果我在课堂上为某些目的需要这些功能,我可以将它添加到类的命名空间中,并在没有&#34;污染&#34;的情况下使用它。我的班级有这个功能。

在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 ++缺少扩展方法,许多人会争论C#缺少命名空间函数。

我认为最好的方法是不要将这些语言与某些细节进行比较,因为它们是不同的语言,具有不同的实现。

正如您在问题中引用的那样,C ++ / CLI不支持 &#34;真正&#34; 支持命名空间成员函数,因为它添加了一个未命名的类来使用它们这与C ++实现没有任何关系,可能与它看起来很接近,但确实不同。

最后,功能始终是值得期待的,而且我希望C#中的命名空间成员函数与我想要的C ++扩展方法一样多。无论如何,这并不多。

相关问题