简体   繁体   English

为什么 C# 默认将方法实现为非虚拟的?

[英]Why C# implements methods as non-virtual by default?

Unlike Java, why does C# treat methods as non-virtual functions by default?与 Java 不同,为什么 C# 默认将方法视为非虚函数? Is it more likely to be a performance issue rather than other possible outcomes?是否更有可能是性能问题而不是其他可能的结果?

I am reminded of reading a paragraph from Anders Hejlsberg about several advantages the existing architecture is bringing out.我想起了阅读 Anders Hejlsberg 的一段关于现有架构带来的几个优点的段落。 But, what about side effects?但是,副作用呢? Is it really a good trade-off to have non-virtual methods by default?默认情况下使用非虚拟方法真的是一个很好的权衡吗?

Classes should be designed for inheritance to be able to take advantage of it. 应该为继承设计类以便能够利用它。 Having methods virtual by default means that every function in the class can be plugged out and replaced by another, which is not really a good thing. 默认情况下使用virtual方法意味着可以插入类中的每个函数并替换为另一个函数,这实际上并不是一件好事。 Many people even believe that classes should have been sealed by default. 很多人甚至认为课程应该默认sealed

virtual methods can also have a slight performance implication. virtual方法也可以有轻微的性能影响。 This is not likely to be the primary reason, however. 然而,这可能不是主要原因。

I'm surprised that there seems to be such a consensus here that non-virtual-by-default is the right way to do things. 我很惊讶这里似乎有这样的共识,即非虚拟默认是正确的做事方式。 I'm going to come down on the other - I think pragmatic - side of the fence. 我会在另一方面下台 - 我认为实用主义的一面。

Most of the justifications read to me like the old "If we give you the power you might hurt yourself" argument. 大多数理由都像旧的“如果我们给你力量,你可能会伤到自己”这样的论点。 From programmers?! 来自程序员?!

It seems to me like the coder who didn't know enough (or have enough time) to design their library for inheritance and/or extensibility is the coder who's produced exactly the library I'm likely to have to fix or tweak - exactly the library where the ability to override would come in most useful. 在我看来,编程人员不够了解(或有足够的时间)设计他们的库以进行继承和/或可扩展性的编码器是完全生成我可能需要修复或调整的库 - 完全是库中的覆盖能力最有用。

The number of times I've had to write ugly, desperate work-around code (or to abandon usage and roll my own alternative solution) because I can't override far, far outweighs the number of times I've ever been bitten (eg in Java) by overriding where the designer might not have considered I might. 我必须编写丑陋,绝望的解决方案代码(或放弃使用并推出我自己的替代解决方案)的次数,因为我无法超越,远远超过我被咬过的次数(例如在Java中)通过覆盖设计者可能没有考虑过的地方。

Non-virtual-by-default makes my life harder. 非虚拟默认让我的生活更加困难。

UPDATE: It's been pointed out [quite correctly] that I didn't actually answer the question. 更新:有人指出[非常正确]我实际上没有回答这个问题。 So - and with apologies for being rather late.... 所以 - 并为迟到而道歉......

I kinda wanted to be able to write something pithy like "C# implements methods as non-virtual by default because a bad decision was made which valued programs more highly than programmers". 我有点想写一些简洁的东西,比如“C#默认情况下将方法实现为非虚拟,因为做出了一个错误的决定,它比程序员更重视程序”。 (I think that could be somewhat justified based on some of the other answers to this question - like performance (premature optimisation, anyone?), or guaranteeing the behaviour of classes.) (我认为根据这个问题的其他一些答案 - 比如性能(过早优化,任何人?),或保证类的行为,这可能有点合理。)

However, I realise I'd just be stating my opinion and not that definitive answer that Stack Overflow desires. 但是,我意识到我只是在陈述我的意见,而不是Stack Overflow所希望的那个明确的答案。 Surely, I thought, at the highest level the definitive (but unhelpful) answer is: 当然,我认为,在最高级别,最终(但没有帮助)的答案是:

They're non-virtual by default because the language-designers had a decision to make and that's what they chose. 默认情况下,它们是非虚拟的,因为语言设计者决定制作,这就是他们所选择的。

Now I guess the exact reason that they made that decision we'll never.... oh, wait! 现在我想他们做出决定的确切原因我们永远不会......哦,等等! The transcript of a conversation! 谈话的成绩单!

So it would seem that the answers and comments here about the dangers of overriding APIs and the need to explicitly design for inheritance are on the right track but are all missing an important temporal aspect: Anders' main concern was about maintaining a class's or API's implicit contract across versions . 因此,似乎这里关于重写API的危险和明确设计继承的必要性的答案和评论都在正确的轨道上,但都缺少一个重要的时间方面:Anders的主要关注点是维护一个类或API的隐含跨版本合同。 And I think he's actually more concerned about allowing the .Net / C# platform to change under code rather than concerned about user-code changing on top of the platform. 而且我认为他实际上更关心的是允许.Net / C#平台在代码下进行更改,而不是担心平台上的用户代码更改。 (And his "pragmatic" viewpoint is the exact opposite of mine because he's looking from the other side.) (而他的“务实”观点与我的完全相反,因为他从另一边看。)

(But couldn't they just have picked virtual-by-default and then peppered "final" through the codebase? Perhaps that's not quite the same.. and Anders is clearly smarter than me so I'm going to let it lie.) (但他们不能只选择虚拟默认,然后通过代码库填写“最终”吗?也许这并不完全相同......而Anders显然比我聪明,所以我会让它撒谎。)

Because it's too easy to forget that a method may be overridden and not design for that. 因为很容易忘记一个方法可能被覆盖而不是为此设计。 C# makes you think before you make it virtual. C#让你在虚拟之前思考。 I think this is a great design decision. 我认为这是一个伟大的设计决定。 Some people (such as Jon Skeet) have even said that classes should be sealed by default. 有些人(比如Jon Skeet)甚至说过应该默认密封课程。

To summarize what others said, there are a few reasons: 总结其他人所说的,有几个原因:

1- In C#, there are many things in syntax and semantics that come straight from C++. 1-在C#中,语法和语义有很多东西直接来自C ++。 The fact that methods where not-virtual by default in C++ influenced C#. 事实上,C ++中默认情况下不是虚拟的方法影响了C#。

2- Having every method virtual by default is a performance concern because every method call must use the object's Virtual Table. 2-默认情况下,每个方法都是虚拟的,这是一个性能问题,因为每个方法调用都必须使用对象的虚拟表。 Moreover, this strongly limits the Just-In-Time compiler's ability to inline methods and perform other kinds of optimization. 此外,这极大地限制了Just-In-Time编译器内联方法和执行其他类型优化的能力。

3- Most importantly, if methods are not virtual by default, you can guarantee the behavior of your classes. 3-最重要的是,如果默认情况下方法不是虚拟的,则可以保证类的行为。 When they are virtual by default, such as in Java, you can't even guarantee that a simple getter method will do as intended because it could be overridden to do anything in a derived class (of course you can, and should, make the method and/or the class final). 当它们在默认情况下是虚拟的时,例如在Java中,你甚至不能保证简单的getter方法会按预期执行,因为它可以被覆盖以在派生类中执行任何操作(当然,你可以而且应该,方法和/或类最终)。

One might wonder, as Zifre mentioned, why the C# language did not go a step further and make classes sealed by default. 正如Zifre所提到的,人们可能会想,为什么C#语言没有更进一步,默认情况下会使类密封。 That's part of the whole debate about the problems of implementation inheritance, which is a very interesting topic. 这是关于实现继承问题的整个辩论的一部分,这是一个非常有趣的话题。

C# is influenced by C++ (and more). C#受C ++(及更多)的影响。 C++ does not enable dynamic dispatch (virtual functions) by default. 默认情况下,C ++不启用动态分派(虚拟功能)。 One (good?) argument for this is the question: "How often do you implement classes that are members of a class hiearchy?". 对此的一个(好的?)论点是这样的问题:“你经常实现属于类层次结构成员的类吗?”。 Another reason to avoid enabling dynamic dispatch by default is the memory footprint. 默认情况下避免启用动态分派的另一个原因是内存占用。 A class without a virtual pointer (vpointer) pointing to a virtual table , is ofcourse smaller than the corresponding class with late binding enabled. 没有指向虚拟表的虚拟指针(vpointer)的类比使用后期绑定的相应类小。

The performance issue is not so easy to say "yes" or "no" to. 性能问题并不容易说“是”或“不”。 The reason for this is the Just In Time (JIT) compilation which is a run time optimization in C#. 原因是Just In Time(JIT)编译,它是C#中的运行时优化。

Another, similar question about " speed of virtual calls.. " 关于“ 虚拟通话速度...... ”的另一个类似问题

The simple reason is design and maintenance cost in addition to performance costs. 除了性能成本之外,简单的原因是设计和维护成本。 A virtual method has additional cost as compared with a non-virtual method because the designer of the class must plan for what happens when the method is overridden by another class. 与非虚方法相比,虚方法具有额外的成本,因为类的设计者必须计划当方法被另一个类重写时发生的事情。 This has a big impact if you expect a particular method to update internal state or have a particular behavior. 如果您希望特定方法更新内部状态或具有特定行为,则会产生很大影响。 You now have to plan for what happens when a derived class changes that behavior. 您现在必须计划派生类更改该行为时会发生什么。 It's much harder to write reliable code in that situation. 在那种情况下编写可靠的代码要困难得多。

With a non-virtual method you have total control. 使用非虚拟方法,您可以完全控制。 Anything that goes wrong is the fault of the original author. 任何出错的都是原作者的错。 The code is much easier to reason about. 代码更容易推理。

If all C# methods were virtual then the vtbl would be much bigger. 如果所有C#方法都是虚拟的,那么vtbl会更大。

C# objects only have virtual methods if the class has virtual methods defined. 如果类定义了虚方法,则C#对象只有虚方法。 It is true that all objects have type information that includes a vtbl equivalent, but if no virtual methods are defined then only the base Object methods will be present. 确实,所有对象都具有包含vtbl等效项的类型信息,但如果没有定义虚拟方法,则只存在基础Object方法。

@Tom Hawtin: It is probably more accurate to say that C++, C# and Java are all from the C family of languages :) @Tom Hawtin:可能更准确地说C ++,C#和Java都来自C系列语言:)

Coming from a perl background I think C# sealed the doom of every developer who might have wanted to extend and modify the behaviour of a base class' thru a non virtual method without forcing all users of the new class to be aware of potentially behind the scene details. 来自perl背景我认为C#密封了每个可能想要通过非虚方法扩展和修改基类行为的开发人员的厄运,而不强迫新类的所有用户都知道可能在幕后细节。

Consider the List class' Add method. 考虑List类的Add方法。 What if a developer wanted to update one of several potential databases whenever a particular List is 'Added' to? 如果开发人员想要在特定列表“添加”时更新几个潜在数据库中的一个,该怎么办? If 'Add' had been virtual by default the developer could develop a 'BackedList' class that overrode the 'Add' method without forcing all client code to know it was a 'BackedList' instead of a regular 'List'. 如果默认情况下“添加”是虚拟的,开发人员可以开发一个“BackedList”类,该类覆盖“添加”方法,而不会强制所有客户端代码知道它是'BackedList'而不是常规'List'。 For all practical purposes the 'BackedList' can be viewed as just another 'List' from client code. 出于所有实际目的,'BackedList'可以被视为来自客户端代码的另一个'List'。

This makes sense from the perspective of a large main class which might provide access to one or more list components which themselves are backed by one or more schemas in a database. 从大型主类的角度来看,这是有道理的,它可以提供对一个或多个列表组件的访问,这些组件本身由数据库中的一个或多个模式支持。 Given that C# methods are not virtual by default, the list provided by the main class cannot be a simple IEnumerable or ICollection or even a List instance but must instead be advertised to the client as a 'BackedList' in order to ensure that the new version of the 'Add' operation is called to update the correct schema. 鉴于C#方法默认情况下不是虚拟的,主类提供的列表不能是简单的IEnumerable或ICollection,甚至不能是List实例,而必须作为'BackedList'广告给客户端,以确保新版本调用“添加”操作以更新正确的模式。

Performance.表现。

Imagine a set of classes that override a virtual base method:想象一组覆盖虚拟基方法的类:

class Base {
   public virtual int func(int x) { return 0; }
}

class ClassA: Base {
   public override int func(int x) { return x + 100; }
}

class ClassB: Base {
   public override int func(int x) { return x + 200; }
}

Now imagine you want to call the func method:现在假设你想调用func方法:

   Base foo;
   //...sometime later...
   int x = foo.func(42);

Look at what the CPU has to actually do:看看 CPU 实际要做的事情:

    mov   ecx, bfunc$ -- load the address of the "ClassB.func" method from the VMT
    push  42          -- push the 42 argument
    call  [eax]       -- call ClassB.func

No problem?没问题? No, problem!没问题!

The assembly isn't that hard to follow:组装并不难理解:

  1. mov ecx, foo$ : This needs to reach into memory, and hit the part of the object's Virtual Method Table (VMT) to get the address of the overridden foo method. mov ecx, foo$ :这需要进入内存,并点击对象的虚拟方法表 (VMT) 的一部分以获取被覆盖的foo方法的地址。 The CPU will begin the fetch of the data from memory, and then it will continue on: CPU 将开始从内存中获取数据,然后继续:
  2. push 42 : Push the argument 42 onto the stack for the call to the function. push 42 :将参数42压入堆栈以调用函数。 No problem, that can run right away, and then we continue to:没问题,可以马上运行,然后我们继续:
  3. call [ecx] Call the address of the ClassB.func function. call [ecx]调用ClassB.func函数的地址。 ← 𝕊𝕋𝔸𝕃𝕃!!! ← 𝕊𝕋𝔸𝕃𝕃!!!

That's a problem.那是个问题。 The address of ClassB.func function has not been fetched from the VMT yet.尚未从 VMT 获取ClassB.func函数的地址。 This means that the CPU doesn't know where the go to next.这意味着 CPU 不知道下一步要去哪里。 Ideally it would follow a jump and continue spectatively executing instructions as it waits for the address of ClassB.func to come back from memory.理想情况下,它会跟随一个jump并继续执行指令,等待ClassB.func的地址从内存中返回。 But it can't;但它不能; so we wait.所以我们等待。

If we are lucky: the data already is in the L2 cache.如果幸运的话:数据已经在 L2 缓存中。 Getting a value out of the L2 cache into a place where it can be used is going to take 12-15 cycles.将值从 L2 缓存中取出到可以使用的地方需要 12-15 个周期。 The CPU can't know where to go next without having to wait for memory for 12-15 cycles. CPU 不知道接下来要去哪里,而不必等待内存 12-15 个周期。

𝕋𝕙𝕖 ℂℙ𝕌 𝕚𝕤 𝕤𝕥𝕒𝕝𝕝𝕖𝕕 𝕗𝕠𝕣 𝟙𝟚-𝟙𝟝 𝕔𝕪𝕔𝕝𝕖𝕤 𝕋𝕙𝕖 ℂℙ𝕌 𝕚𝕤 𝕤𝕥𝕒𝕝𝕝𝕖𝕕 𝕗𝕠𝕣 𝟙𝟚-𝟙𝟝 𝕔𝕪𝕔𝕝𝕖𝕤

Our program is stuck doing nothing for 12-15 cycles.我们的程序在 12-15 个周期内无所事事。

The CPU core has 7 execution engines. CPU 内核有 7 个执行引擎。 The main job of the CPU is keeping those 7 pipelines full of stuff to do. CPU 的主要工作是让这 7 条管道充满要做的事情。 That means:这意味着:

  • JITing your machine code into a different order将您的机器代码 JIT 转换为不同的顺序
  • Starting the fetch from memory as soon as possible, letting us move on to other things尽快开始从内存中提取,让我们继续做其他事情
  • executing 100, 200, 300 instructions ahead.提前执行 100、200、300 条指令。 It will be executing 17 iterations ahead in your loop, across multiple function call and returns它将在您的循环中提前执行 17 次迭代,跨越多个函数调用和返回
  • it has a branch predictor to try to guess which way a comparison will go, so that it can continue executing ahead while we wait.它有一个分支预测器来尝试猜测比较会走哪条路,这样它就可以在我们等待的同时继续执行。 If it guesses wrong, then it does have to undo all that work.如果它猜错了,那么它必须撤消所有这些工作。 But the branch predictor is not stupid - it's right 94% of the time.但是分支预测器并不愚蠢——94% 的时间都是正确的。

Your CPU has all this power, and capability, and it's just STALLED FOR 15 CYCLES!?您的 CPU 拥有所有这些功能和能力,而且它只是停滞了 15 个周期!?

This is awful.这太可怕了。 This is terrible.这真糟糕。 And you suffer this penalty every time you call a virtual method - whether you actually overrode it or not.每次调用virtual方法时都会受到这种惩罚——不管你是否真的覆盖了它。

Our program is 12-15 cycles slower every method call because the language designer made virtual methods opt-out rather than opt-in.我们的程序每次方法调用慢了 12-15 个周期,因为语言设计者让虚拟方法选择退出而不是选择加入。

This is why Microsoft decided to not make all methods virtual by default: they learned from Java's mistakes.这就是微软决定不让所有方法默认为虚拟的原因:他们从 Java 的错误中吸取了教训。

Someone ported Android to C#, and it was faster有人将Android移植到C#,而且速度更快

In 2012, the Xamarin people ported all of Android's Dalvik (ie Java) to C#. 2012 年,Xamarin 人将所有 Android 的 Dalvik(即 Java)移植到 C#。 From them:他们那里:

Performance表现

When C# came around, Microsoft modified the language in a couple of significant ways that made it easier to optimize.当 C# 出现时,Microsoft 以几种重要的方式修改了该语言,使其更易于优化。 Value types were introduced to allow small objects to have low overheads and virtual methods were made opt-in, instead of opt-out which made for simpler VMs.引入了值类型以允许小对象具有较低的开销,并且虚拟方法被选择加入,而不是选择退出,这使得更简单的 VM。

(emphasis mine) (强调我的)

It is certainly not a performance issue. 这当然不是性能问题。 Sun's Java interpreter uses he same code to dispatch ( invokevirtual bytecode) and HotSpot generates exactly the same code whether final or not. Sun的Java解释器使用相同的代码来调度( invokevirtual字节码),HotSpot生成完全相同的代码,无论是否为final I believe all C# objects (but not structs) have virtual methods, so you are always going to need the vtbl /runtime class identification. 我相信所有C#对象(但不是结构)都有虚方法,因此您总是需要vtbl / runtime类标识。 C# is a dialect of "Java-like languages". C#是“类Java语言”的方言。 To suggest it comes from C++ is not entirely honest. 建议它来自C ++并不完全诚实。

There is an idea that you should "design for inheritance or else prohibit it". 有一种想法,你应该“设计继承或禁止它”。 Which sounds like a great idea right up to the moment you have a severe business case to put in a quick fix. 这听起来像个好主意,直​​到你有一个严厉的商业案例来进行快速修复。 Perhaps inheriting from code that you don't control. 也许继承自你无法控制的代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在C#中模拟非虚拟方法 - Mocking non-virtual methods in C# C#中的非虚拟方法,静态绑定和接口 - Non-virtual methods, static binding, and interface in C# 如果默认情况下方法既不是虚拟的也不是密封的,那么为什么这个方法覆盖了非虚拟和非密封的方法? - If methods are neither virtual nor sealed by default, then why does this method override of a non-virtual and non-sealed method work? C#/ Resharper 5结构搜索,检测并警告具有某些属性的类上是否存在任何非虚拟公共方法 - C#/Resharper 5 structural search, detect and warn if any non-virtual public methods on classes with certain attributes 基类中的非虚拟C#方法是否还会产生vtable开销? - Do non-virtual C# methods in a base class still incur vtable overheads? C#/ C ++中的非虚拟接口设计模式 - Non-virtual interface design pattern in C#/C++ 为什么C#没有非虚拟调用约定? - Why doesn't C# have a non-virtual calling convention? c#实现接口的类中的虚方法 - c# virtual methods in class that implements interface C#调用非虚拟实现的接口方法 - C# call an interface method non-virtual implementation 导致“ C#虚拟或抽象成员不能为私有”的非虚拟,非抽象方法 - Non-virtual, non-abstract method causing “C# virtual or abstract members cannot be private”
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM