Why C# implements methods as non-virtual by default?
Asked Answered
B

10

108

Unlike Java, why does C# treat methods as non-virtual functions by default? 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. But, what about side effects? Is it really a good trade-off to have non-virtual methods by default?

Bathroom answered 2/5, 2009 at 14:28 Comment(3)
The answers that mention performance reasons overlook the fact that the C# compiler mostly compiles method calls to callvirt and not call. Which is why in C# it's not possible to have a method that behaves differently if the this reference is null. See here for more info.Kieserite
True! call IL instruction is mostly for calls made to static methods.Rensselaerite
C#'s architect Anders Hejlsberg's thoughts here and here.Rensselaerite
F
103

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. Many people even believe that classes should have been sealed by default.

virtual methods can also have a slight performance implication. This is not likely to be the primary reason, however.

Foley answered 2/5, 2009 at 14:31 Comment(18)
Personally, I doubt that part about performance. Even for virtual functions, the compiler is very able to figure out where to replace callvirt by a simple call in the IL code (or even further downstream when JITting). Java HotSpot does the same. The rest of the answer is spot-on.Sensitivity
I agree performance is not at the forefront, but it can have performance implications too. It's probably very small. Certainly, it's not the reason for this choice, however. Just wanted to mention that.Foley
Sealed classes make me want to punch babies.Gatian
@mxmissile: me too, but good API design is hard anyway. I think sealed-by-default makes perfect sense for code you own. More often than not, the other programmer on your team did not consider inheritance when they implemented that class you need, and sealed-by-default would convey that message quite well.Gilbertgilberta
I think there is a large performance consideration here: virtual methods cannot be inlined unless you do a lot of fancy stuff to determine the actual runtime type profiles of the code. Hotspot specializes in just this sort of thing which is why it starts out by interpreting the code before using the information it gathers to compile it. A basic tenet of good API design is to program to interfaces rather than implementation classes which will always require virtual calls through the interface anyway.Dominiquedominium
This has given me a TON of pain where I want to rewrite a simple property to do some extra stuff when its set (naturally, I COULD just write a new method - but the original base class doesn't have that method so it doesn't work so well since the code utilizes polymorphism in lots of lists). Trusting the programmer to mark the right stuff as virtual has implications for libraries that hurt the end user.Recess
Many people are psychopaths too, doesn't mean we should listen to them. Virtual should be the default in C#, code should be extensible without having to change implementations or completely re-write it. People who overprotect their APIs often end up with dead or half-used APIs which is far worse than the scenario of someone misusing it.Valenciavalenciennes
Different languages are designed with different goals in mind. This design decision fits quite well with the design goals of C#.Foley
I would like to see this question/answer reviewed by the Java designers (Josh Bloch & Co.) What would they say?Topo
May be its better for consistency to have either both classes sealed and methods non-virtual or both classes non-sealed and methods virtual by default. Personally I rate all overriding a code smell, the very capability itself.Duhl
Considering how many frameworks and methodologies require virtual properties and methods at this point (many ORM's, mocking frameworks, testing suites) that are considered "best practices" or modern, vetted patterns, it seems C# has definitely gone down the wrong path with this.Ethbin
Sealed and virtual are not the only options. Sealed means, "I thought about the design, and you're not allowed to extend this." Virtual means, "I thought about the design, and you're allowed to extend this." Not having sealed nor virtual means, "I didn't think about the design, so extend this at your own risk." You can override anything in C#, but some thing require more hurdles than others. Extending a class not meant for polymorphism is a hack, in any language, C# just makes you admit it.Protozoal
@xero: Virtual also means "I didn't think about whether anyone will ever want to mock, stub, intercept, log, or decorate this method call or any of its inheritants", which I think is a good thing.Premillennial
@notfed That's why you define an interface. And if the API developer didn't, you can easily create your own with a wrapper for the real thing. Honestly its not that hard. I also wonder why so many people complain about sealed classes; makes me think people are too quick to think the best answer is inheritance when it rarely is (favor composition over inheritance, right?).Kieserite
You said: "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." Why?Loosing
@Loosing Because it is difficult to do that correctly. When you are writing a class and need to assume that every piece of it is replaceable, there are few invariants you can rely on.Foley
If a piece of it is not replaceable then you should make a private method. If you need a public method then you can make it static or final. Static has a benefit that a user can see from the client code that that method is related to exact class and that it is not overridden by subclass.Loosing
@Loosing "If you need a public method then you can make it static or final" The public/private argument is red herring. The question is regardless of public/private, which one should be the default: sealed/final or not.Foley
D
93

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 (e.g. in Java) by overriding where the designer might not have considered I might.

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". (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. 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. 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. (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.)

Demulsify answered 18/6, 2010 at 11:23 Comment(5)
I enthusiastically agree. If you're going to publish an API (whether internally in a company or to the external world), you really can and should make sure your code is designed for inheritance. You should be making the API good and polished if you're publishing it to be used by many people. Compared to the effort needed for good overall design (good content, clear use cases, testing, documentation), designing for inheritance really isn't too bad. And if you're not publishing, virtual-by-default is less time-consuming, and you can always fix the small portion of cases where it's problematic.Inexactitude
Now if there was only a editor feature in Visual Studio to automatically tag all methods/properties as virtual... Visual Studio add-in anyone?Topo
@Chris Morgan: Oops - true! I guess that I (and everyone who liked my answer) must have arrived here with some frustration to vent and never really noticed. Your comment has been nagging at the perfectionist in me for the last few days though, so I'm going to add one (that took me way longer to put together than I ever wanted).Demulsify
Considering modern development practices like dependency injection, mocking frameworks, and ORMs, it seems clear that our C# designers missed the mark a bit. It's very frustrating to have to test a dependency when you cannot override a property by default.Ethbin
A good example of where desperate workarounds have been done is the case of C#'s Moq versus Java's MockitoPremillennial
C
20

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. I think this is a great design decision. Some people (such as Jon Skeet) have even said that classes should be sealed by default.

Carmencita answered 2/5, 2009 at 14:35 Comment(0)
E
13

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++. The fact that methods where not-virtual by default in C++ influenced C#.

2- Having every method virtual by default is a performance concern because every method call must use the object's Virtual Table. Moreover, this strongly limits the Just-In-Time compiler's ability to inline methods and perform other kinds of optimization.

3- Most importantly, if methods are not virtual by default, you can guarantee the behavior of your classes. 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).

One might wonder, as Zifre mentioned, why the C# language did not go a step further and make classes sealed by default. That's part of the whole debate about the problems of implementation inheritance, which is a very interesting topic.

Eudemonia answered 2/5, 2009 at 14:41 Comment(1)
I would have preferred classes sealed by default too. Then it would be consistent.Kieserite
G
9

C# is influenced by C++ (and more). C++ does not enable dynamic dispatch (virtual functions) by default. 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.

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#.

Another, similar question about "speed of virtual calls.."

Greasewood answered 2/5, 2009 at 14:29 Comment(2)
I am a bit doubtful that virtual methods have performance implications for C#, because of the JIT compiler. That's one of the area where JIT can be better than offline compilation, because they can inline function calls which are "unknown" before runtimeDowie
Actually, I think it's more influenced by Java than C++ which does that by default.Foley
S
7

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.

Stradivarius answered 2/5, 2009 at 14:46 Comment(1)
This is a really old post but for a newbie who is writing his first project I constantly fret over unintended/unknown consequences of the code I write. It's very comforting knowing that non-virtual methods are totally MY fault.Toner
V
2

If all C# methods were virtual then the vtbl would be much bigger.

C# objects only have virtual methods if the class has virtual methods defined. 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.

@Tom Hawtin: It is probably more accurate to say that C++, C# and Java are all from the C family of languages :)

Vaporescence answered 2/5, 2009 at 20:10 Comment(1)
Why would it be a problem for the vtable to be much bigger? There's only 1 vtable per class (and not per instance), so its size doesn't make a whole lot of difference.Inexactitude
H
1

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.

Consider the List class' Add method. 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'. For all practical purposes the 'BackedList' can be viewed as just another 'List' from client code.

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.

Halfblood answered 2/5, 2009 at 14:28 Comment(3)
True, virtual methods by default makes job easier, but does it make sense? There are many things you could employ to make life easier. Why not just public fields in classes? Altering its behaviour is too easy. In my opinion everything in language should be strictly encapsulated, rigid and resilient to change by default. Change it only if required. Just being lil' philosophical abt design decisions. Continued..Duhl
...To talk on current topic, Inheritance model should be used only if B is A. If B requires something different from A then its not A. I believe overriding as a capability in the language itself is a design flaw. If you need a different Add method, then your collection class is not a List. Trying to tell it is is faking. The right approach here is composition (and not faking). True the whole framework is built on overriding capabilities, but I just dont like it.Duhl
I think I understand your point, but on the concrete example provided: 'BackedList' could just implement the interface 'IList' and the client only knows about the interface. right? am i missing something? however I do understand the broader point you are trying to make.Boondocks
B
0

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. I believe all C# objects (but not structs) have virtual methods, so you are always going to need the vtbl/runtime class identification. C# is a dialect of "Java-like languages". To suggest it comes from C++ is not entirely honest.

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.

Buckingham answered 2/5, 2009 at 14:47 Comment(2)
Hotspot is forced to go to huge lengths to do that optimisation, precisely because all methods are virtual by default, which has a huge performance impact. CoreCLR is able to achieve similar performance whilst being far simplerCorsica
@YairHalberstadt Hotspot needs to be able to back out compiled code for a variety of other reasons. It's years since I've looked at the source, but the difference between final and effectively final methods is trivial. It's also worth noting that it can do bimorphic inlining, that is inline methods with two different implementations.Buckingham
F
0

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:

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

Look at what the CPU has to actually do:

    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. The CPU will begin the fetch of the data from memory, and then it will continue on:
  2. push 42: Push the argument 42 onto the stack for the call to the function. No problem, that can run right away, and then we continue to:
  3. call [ecx] Call the address of the ClassB.func function. ← 𝕊𝕋𝔸𝕃𝕃!!!

That's a problem. The address of ClassB.func function has not been fetched from the VMT yet. This means that the CPU doesn't know where the go to next. 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. But it can't; so we wait.

If we are lucky: the data already is in the L2 cache. Getting a value out of the L2 cache into a place where it can be used is going to take 12-15 cycles. The CPU can't know where to go next without having to wait for memory for 12-15 cycles.

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

Our program is stuck doing nothing for 12-15 cycles.

The CPU core has 7 execution engines. The main job of the CPU is keeping those 7 pipelines full of stuff to do. That means:

  • JITing your machine code into a different order
  • Starting the fetch from memory as soon as possible, letting us move on to other things
  • executing 100, 200, 300 instructions ahead. It will be executing 17 iterations ahead in your loop, across multiple function call and returns
  • 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.

Your CPU has all this power, and capability, and it's just STALLED FOR 15 CYCLES!?

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.

Our program is 12-15 cycles slower every method call because the language designer made virtual methods opt-out rather than opt-in.

This is why Microsoft decided to not make all methods virtual by default: they learned from Java's mistakes.

Someone ported Android to C#, and it was faster

In 2012, the Xamarin people ported all of Android's Dalvik (i.e. Java) to C#. From them:

Performance

When C# came around, Microsoft modified the language in a couple of significant ways that made it easier to optimize. 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.

(emphasis mine)

Freedom answered 10/1, 2022 at 0:32 Comment(10)
They do have a very efficient map. "You want to call function x? Here is its address." The problem is a map. The fact that you have to have a map at all costs you 2 cpu cycles - because i have to JMP to that address i just read. The other problem with a map is that it has to exist in memory, which means you have to read the address of that function from memory. Reading from memory, if you're really lucky can take 32 cpu cycles. So why waste 32 cpu cycles, or 2 cpu cycles, when you can waste 0 cpu cycles.Freedom
If you are at this level of optimization my best bet would to say you "Change the language you use. C / C++, or even better : Rust. They are more appropriate languages for controlling how things happen. And you will have more potential performance gains that what you could save about virtual functions.Sapotaceous
@Sapotaceous Why would they not make your code faster for free? Also, the language you use isn't important to performance anymore (youtube.com/watch?v=qpYdsjMaoRg&t=2700s). The x86 CPU is rewriting your compiled machine code anyway, jitting it to RISC microcode, changing the order to instructions, speculativly executing 200-300 instructions ahead, while it waits for memory. Memory is everything. And having to hit memory in order to lookup in the VTable the address of the next instruction (because it's virtual) is the bottleneck. C/C++/Rust/C# - doesn't matter much anymore.Freedom
And C++ suffers from the same problem: having to look up the address of the next instruction to begin speculativly executing in a virtual method table means the CPU has to stall. You have all this silicon, multiple execution units, branch predictors, speculative execution, and all of it has to stall because it has to wait the 17 cycles for the address of your virtual function to come back from the L2 cache. It is an absolute truth that virtual functions make things slower. Using a different langauge doesn't change that. If you case about performance: don't make it virtual.Freedom
But of course you already knew all this already because it's all in the answer you already read.Freedom
Because we call this prematured optimization. With time, on project with people that tries to optimize everyting, you end up with a lot of complex things in code that are not prooved to bring some real performance or added value (i mean "pragmatically prooved to be interesting for "end user"). When I was young I used to think like you do but with XP you optimize your own time of life /work: you spent it to maximise added value / cost time. There's no point spending 2h to make speed 0.001% better (by removing some "virtual" keyword). Do benchmarksSapotaceous
Think about the big picture instead and you will find there is big bottlenecks (business definition of the problem and/or badly designed solution, so implem is not the most important thing to improve). Details are important for stability / resiliency / error handling (=> less boring stuff to do), Performance things like you talk about is only for very specific and rare scenarios like coding ultra high frequency trading system.... But in that case.. yes you do Rust or C, not C++. because choosing thone of these languages is exactly a good choice of design...Sapotaceous
Since this is a programming web-site, and we're here to teach you, watch this: youtube.com/watch?v=qpYdsjMaoRg "Performance things like you talk about is only for very specific". Precisely. So when a developer needs it: it's there.Freedom
You are mixing things, i am talking about vtable optimization. and you vectorization optimization. Of course vectorization is an important optimization (at work I get factor 70x JUST by seeing/fixing the whole things, not using any low level instructions). In your video at ~37:00 the guys talk about "Compilator-driven speculative devirtualization & inlining"... So thanks for providing me an argument :) Try to fix the big picture, how things are orchestrated, instead of low level thing like this. Because compilers are smarter than you in general.Sapotaceous
The most optimized vtable code is the code that doesn't even run (because the compiler known the call target at compile time; and doesn't have to suffer any memory latency)Freedom

© 2022 - 2024 — McMap. All rights reserved.