Versioning, Virtual, and Override

A Conversation with Anders Hejlsberg, Part IV

by Bill Venners with Bruce Eckel
September 15, 2003

Summary
Anders Hejlsberg, the lead C# architect, talks with Bruce Eckel and Bill Venners about why C# instance methods are non-virtual by default and why programmers must explicitly indicate an override.

Anders Hejlsberg, a distinguished engineer at Microsoft, led the team that designed the C# (pronounced C Sharp) programming language. Hejlsberg first vaulted onto the software world stage in the early eighties by creating a Pascal compiler for MS-DOS and CP/M. A very young company called Borland soon hired Hejlsberg and bought his compiler, which was thereafter marketed as Turbo Pascal. At Borland, Hejlsberg continued to develop Turbo Pascal and eventually led the team that designed Turbo Pascal's replacement: Delphi. In 1996, after 13 years with Borland, Hejlsberg joined Microsoft, where he initially worked as an architect of Visual J++ and the Windows Foundation Classes (WFC). Subsequently, Hejlsberg was chief designer of C# and a key participant in the creation of the .NET framework. Currently, Anders Hejlsberg leads the continued development of the C# programming language.

On July 30, 2003, Bruce Eckel, author of Thinking in C++ and Thinking in Java, and Bill Venners, editor-in-chief of Artima.com, met with Anders Hejlsberg in his office at Microsoft in Redmond, Washington. In this interview, which will be published in multiple installments on Artima.com and on an audio CD-ROM to be released this fall by Bruce Eckel, Anders Hejlsberg discusses many design choices of the C# language and the .NET framework.

  • In Part I: The C# Design Process, Hejlsberg discusses the process used by the team that designed C#, and the relative merits of usability studies and good taste in language design.
  • In Part II: The Trouble with Checked Exceptions, Hejlsberg discusses versionability and scalability issues with checked exceptions.
  • In Part III: Delegates, Components, and Simplexity, Hejlsberg discusses delegates and C#'s first class treatment of component concepts.
  • In this fourth installment, Hejlsberg explains why C# instance methods are non-virtual by default and why programmers must explicitly indicate an override.

Non-Virtual is the Default

Bill Venners: In Java, instance methods are virtual by default—they can be overridden in subclasses unless they are explicitly declared final. In C#, by contrast, instance methods are non-virtual by default. To make a method virtual, the programmer must explicitly declare it virtual. Why is non-virtual the default in C#?

Anders Hejlsberg: There are several reasons. One is performance. We can observe that as people write code in Java, they forget to mark their methods final. Therefore, those methods are virtual. Because they're virtual, they don't perform as well. There's just performance overhead associated with being a virtual method. That's one issue.

A more important issue is versioning. There are two schools of thought about virtual methods. The academic school of thought says, "Everything should be virtual, because I might want to override it someday." The pragmatic school of thought, which comes from building real applications that run in the real world, says, "We've got to be real careful about what we make virtual."

When we make something virtual in a platform, we're making an awful lot of promises about how it evolves in the future. For a non-virtual method, we promise that when you call this method, x and y will happen. When we publish a virtual method in an API, we not only promise that when you call this method, x and y will happen. We also promise that when you override this method, we will call it in this particular sequence with regard to these other ones and the state will be in this and that invariant.

Every time you say virtual in an API, you are creating a call back hook. As an OS or API framework designer, you've got to be real careful about that. You don't want users overriding and hooking at any arbitrary point in an API, because you cannot necessarily make those promises. And people may not fully understand the promises they are making when they make something virtual.

Incoming and Outgoing Contracts

Bill Venners: It sounds like your not as concerned that the person overriding the method won't correctly implement the promises you've made to the caller. You are worried about all the promises you have to make to the person overriding the method.

Anders Hejlsberg: I'm concerned with both, actually. Virtual has two sides to it: the incoming and the outgoing. People are very good about thinking about what the contract is on the incoming. They are very bad about thinking about the outgoing contract.

Bill Venners: What do you mean by incoming and outgoing contract?

Anders Hejlsberg: The incoming contract is what I use when call a method. It enables me to reason about what I am required to do before I call into the method and what has happened after the method returns. The outgoing contract is what I use when I override a method. If you look at most APIs they are actually very bad at documenting what you're supposed to do when you override a virtual method: What are the invariants before you're called? What should be true after? Which methods can you not call from your implementation? And so on. I think it is very dangerous to say that by default all methods have both incoming and outgoing aspects. It is dangerous to trust everyone to write good documentation about when to override and what the invariants are when overriding.

I can demonstrate to you a very real world versioning problem, one that indeed we see now from experience with Java. Whenever they ship a new version of the Java class libraries, breakage occurs. Whenever they introduce a new method in a base class, if someone in a derived class had a method of that same name, that method is now an override—except if it has a different return type, it no longer compiles. The problem is that Java, and also C++, does not capture the intent of the programmer with respect to virtual.

Virtual Has Two Meanings

Anders Hejlsberg: When you say "virtual," you can mean one of two things. If you did not inherit a method of the same signature, then this is a new virtual method. That's one meaning. Otherwise it is an override of an inherited method. That's the other meaning.

From a versioning perspective, it is important that the programmer indicate their intent when they declare a method virtual. In C#, for example, you must explicitly indicate which meaning of virtual you intend. To declare a new virtual method, you just mark it virtual. But to override an existing virtual method, you must say override.

As a result, C# doesn't have the particular versioning problem I described earlier in which we introduce a method in a base class that you already have in a derived class. In your class, you would have declared foo virtual. Now we introduce a new virtual foo. Well, that's fine. Now there are two virtual foos. There are two VTBL slots. The derived foo hides the base foo, but that's fine. The base foo wasn't even there when the derived foo was written, so it's not like there's anything wrong with hiding this new functionality. And things continue to work the way they're supposed to.

Bruce Eckel: So you saw this versioning problem happen often enough in practice, that you decided to address it? I remember you did a similar thing in Delphi.

Anders Hejlsberg: Yes.

Bruce Eckel: You're perspective on languages is quite different from the other people that I've talked to. It's very pragmatic.

Anders Hejlsberg: I have always described myself as a pragmatic guy. It's funny, because versioning ended up being one of the pillars of our language design. It shows up in how you override virtual methods in C#. Also, the way we do overload resolution in C# is different from any other language I know of, for reasons of versioning. Whenever we looked at designing a particular feature, we would always cross check with versioning. We would ask, "How does versioning change this? How does this function from a versioning perspective?" It turns out that most language design before has given very little thought to that.

Bruce Eckel: Were you concerned about versioning primarily because of the DLL hell problem?

Anders Hejlsberg: Yes, but also because of changes I've observed over the years. Ten to fifteen years ago, back when we had the 640K barrier, heck, you could just throw all your code away every year and rewrite it. The rewrite took about a year, and that coincided with the next release. So you didn't really have to worry about reusing the old stuff. Versioning? What's that? We started from scratch every time.

I hate to break it to you, but that's gone. We will never catch up. We will never fill all the capacity that Moore's law is heaping on us. The way we get more functionality these days is by doing more and more leveraging of existing infrastructure and existing applications. As systems are becoming longer lived, versioning is becoming more important.

Next Week

Come back Monday, September 22 for part II of a conversation with Java's creator James Gosling. I am now staggering the publication of several interviews at once, to give the reader variety. The next installment of this interview with Anders Hejlsberg will appear on Monday, September 29. If you'd like to receive a brief weekly email announcing new articles at Artima.com, please subscribe to the Artima Newsletter.

Resources

Deep Inside C#: An Interview with Microsoft Chief Architect Anders Hejlsberg:
http://windows.oreilly.com/news/hejlsberg_0800.html

A Comparative Overview of C#:
http://genamics.com/developer/csharp_comparative.htm

Microsoft Visual C#:
http://msdn.microsoft.com/vcsharp/

Anders Hejlsberg was not the first Artima interviewee to mention taste. Jim Waldo made almost an identical comment about building a team of tasteful programmers in his interview:
http://www.artima.com/intv/waldo10.html

And an entire portion of Ken Arnold's interview was devoted to design taste - Taste and Aesthetics:
http://www.artima.com/intv/taste.html

Talk back!

Have an opinion? Readers have already posted 31 comments about this article. Why not add yours?

About the authors

Bruce Eckel (www.BruceEckel.com) provides development assistance in Python with user interfaces in Flex. He is the author of Thinking in Java (Prentice-Hall, 1998, 2nd Edition, 2000, 3rd Edition, 2003, 4th Edition, 2005), the Hands-On Java Seminar CD ROM (available on the Web site), Thinking in C++ (PH 1995; 2nd edition 2000, Volume 2 with Chuck Allison, 2003), C++ Inside & Out (Osborne/McGraw-Hill 1993), among others. He's given hundreds of presentations throughout the world, published over 150 articles in numerous magazines, was a founding member of the ANSI/ISO C++ committee and speaks regularly at conferences.

Bill Venners is president of Artima Software, Inc. and editor-in-chief of Artima.com. He is author of the book, Inside the Java Virtual Machine, a programmer-oriented survey of the Java platform's architecture and internals. His popular columns in JavaWorld magazine covered Java internals, object-oriented design, and Jini. Bill has been active in the Jini Community since its inception. He led the Jini Community's ServiceUI project that produced the ServiceUI API. The ServiceUI became the de facto standard way to associate user interfaces to Jini services, and was the first Jini community standard approved via the Jini Decision Process. Bill also serves as an elected member of the Jini Community's initial Technical Oversight Committee (TOC), and in this role helped to define the governance process for the community. He currently devotes most of his energy to building Artima.com into an ever more useful resource for developers.