This site contains older material on Eiffel. For the main Eiffel page, see http://www.eiffel.com.

The significance of .NET

Bertrand Meyer

        A variant of this article appeared in Software Development Magazine, November 2000, as part of the "Beyond Objects" column alternatively written by Clemens Szyperski, Grady Booch and Bertrand Meyer (and originally Bruce Powel Douglass). ©Software Development Magazine, 2000. The present version is the original, pre-copy-editing.

Many of the first press reactions to Microsoft's July announcement of the .NET framework were (surprisingly for anyone who knew the technology) remarkably condescending and dismissive, tending to treat the whole thing as hype. Bob Metcalfe mocked .NET in his InfoWorld column (http://www.infoworld.com/articles/op/xml/00/07/03/000703opmetcalfe.xml) as "a ton of vaporware that even Microsoft does not expect to ship for years". Having pledged to eat his December 1995 column if reality did not bear his prediction that the Internet would collapse in 1996, Metcalfe can now make all the predictions he wants. The truth is that .NET fundamentally changes the state of the art in software development. In fact it has a direct bearing on many of the issues we have debated in this column over the past year: the true nature of components, their relation to the classes and objects of O-O technology, how to document components, the role of Interface Definition Languages, how to combine components from different languages. It doesn't mean you have to like everything in .NET (and I will state my disagreements with some of its technical choices); but it does mean that it will be impossible to discuss issues of component-based development in the same way after the advent of this technology. This is why I am devoting this column to .NET, or more specifically - since the technology has many other contributions, ranging from Web services to database access, version management and development environments - to its effect on component issues.

The architecture

At the center of the .NET framework is an object model, called the VOS (Virtual Object System); at the center of that object model is a type system. This already sets .NET apart from the other component models available, which are organized around a programming language (Java), an application interconnection model (CORBA), a wiring model (COM). An O-O enthusiast like me is entitled to see .NET as the latest vindication of the observation that, in today's software world, there is no going around the O-O model as a foundation for any serious software engineering work. For all of Clemens Szyperski's insistence that components go "beyond objects" (also, incidentally, the global title for this column!), what we end up with in the most recent component model - and, in my opinion, the most impressive one so far - is a notion of component directly based on classes.

What exactly is this object model? If you are a programmer in any of the current object-oriented languages, you already know it, and you don't. You know it because if you program in C++, Java or Eiffel you will immediately recognize the basic concepts of class, inheritance, dynamic binding, class-based typing and so on. You don't because it is not identical to the object model of any one of these languages. Rather, it is an attempt to define a suitable common base that can serve as a bridge between all these languages, and others.

Although I regret some of the specific choices made in the design of the current object model, no one can deny that it is better than either the C++ or the Java model. In particular, the type system of .NET gives objects of predefined basic types such as integers and characters a clear place in the type system; and, using techniques very similar to Eiffel's notion of "expanded type", it provides a clean way to convert back and forth between reference and value types through "boxing" and "unboxing" operations. The result is a much more coherent and regular type system than what we have seen in the dominant languages so far.

Most importantly, this model is designed to be truly language-independent. Press reports have devoted considerable attention to the new C# (C-sharp) language introduced together with .NET. But C# is a means rather than an end: it's a human-usable language that directly reflects the .NET object model. No less and no more. No less because it's a credible competitor to Java (to the point that Don Box, in his keynote at TOOLS USA, quipped that everyone outside of Microsoft should call it "Java 3"), and will undoubtedly attract a significant subset of the Java programmer community. No more because Microsoft is not on a crusade to convert the world to a new programming language. The focus in on the model. .NET is truly multi-language and the role of the framework is to provide a reasonable target to which all current languages can map. In particular, it is clear that one of the internal incentives for designing the framework was the decision to avoid huge duplication of efforts, within Microsoft, by the development groups for the various so-called "Microsoft languages", including Visual C++, Visual Basic, Jscript, and what was to become C#. The framework enables compilers for all these languages to share a common back-end. It took real vision on the part of the Microsoft architects to move to the next logical step: open up this back end to other languages. ISE was privileged to be one of the outside language providers selected, more than a year before the first official announcement of .NET, to port their technology to the new platform, in collaboration with Microsoft; this enabled us to announce Eiffel# - our .NET offering - at the same time as the general announcement. Other third-party languages ported or being ported to .NET include Cobol, APL, Perl, Python, Smalltalk, as well as research languages such as Haskell, ML, Oberon, Scheme and Mercury. An eclectic mix indeed!

More on the object model

I mentioned above that I have my reservations about some of the aspects of the .NET model. Mostly, they follow from the observation that some decisions perpetuate misguided choices of the C-C++-Java line. This is the dominant line today, so it's not surprising that the designers of the framework should have retained a high degree of compatibility with it, to make sure that they don't lose the legions of programmers used to these languages. But that doesn't make these choices right. Examples include: the presence of overloading, a vanity mechanism that brings nothing to the semantic power of an O-O language, but hampers readability and complicates everyone's task; lack of support for multiple inheritance, except (Java style) for "interfaces"; the C++ mechanism for constructors (based on using a single name for all constructors for a class, disambiguating clashes through an irrelevant property, type signatures, and forcing the initialization of an object to go through the initialization procedures for all ancestor types); novariance of routine arguments and results; the difficulty of keeping a clear separation between member names and member values (to support, for example, renaming as in Eiffel). I understand the source of these problems: the .NET designers are not responsible for C++. But what these decisions mean is, in many cases, forcing a complicated solution where simple ones are readily available.

Some of these are matters of taste. In the Eiffel case, the only major problem is multiple inheritance. Not surprisingly, C++ has the same problem; as a result, the current .NET implementation of C++ permits multiple inheritance only in "umanaged" mode (I will explain "managed" and "unmanaged" below).  We want to generate managed code, so we have had to devise rather intricate solutions. Too bad; as anyone who has observed Java and Smalltalk programmers knows, removing multiple inheritance from an O-O language leads programmers to emulate it manually through the kind of tricks that should not be further described in a family-oriented column. By the way, please do not pay attention to anyone who tells you that multiple inheritance is inherently "bad" or "dangerous" or "messy". There is one and only one reason Java and .NET don't have MI: it makes dynamic class loading far more difficult to implement. Every other reason is a red herring. (Harangue mode off.)

More important than these disagreements, however, is the power and versatility of the object model, which has enabled us, as well as the teams for numerous other languages listed above, to port our compilers to .NET in just a few months.

You may be surprised that I didn't list Design by Contract and generic classes (templates in C++ terminology) among the desirable features not supported. (Genericity is under consideration for a later release of the .NET framework, but even if the proposal goes through the result won't be available for a while.) In fact, we don't really mind their absence because, in our Eiffel# compiler, we have been able to program them on top of the .NET framework. So Eiffel# does support generic classes, and the full extent of contract mechanisms - class invariants, preconditions, postconditions. (I'm tempted to use the slogan WE ARE THE "E" IN ".NET", but that would probably offend too many people at once.) This is one of the strengths of the .NET approach: it provides the right basis to implement a number of different approaches to software engineering.

The virtual machine

Based on the object model, .NET offers a virtual machine, the Common Language Runtime. Of course this is not a new idea; Smalltalk has had a virtual machine for a long time, Eiffel provides one (in "workbench" mode), and the Java architecture is based on a virtual machine. As in these other approaches, the Common Language Runtime is able to execute a specific instruction set, known here as MSIL (MicroSoft Intermediate Language), the direct embodiment of the VOS object model. Porting a compiler to .NET means, among other things, retargeting it to generate MSIL instead of, or along with, machine code or some other intermediate language. One of the distinctive traits of the virtual machine is that, unlike its Java counterpart, it does not provide an interpreter: MSIL is meant for on-the-fly compilation to machine code the first time each MSIL unit is executed. This is also known as "Jitting the code", where "Jit" stands for Just In Time compilation. Here .NET draws the lessons of the Java experience (familiar enough to anyone who has ever seen the dreaded message "Starting Java" show up in the browser) by putting performance concerns at the heart of design goals. The results are impressive: we have seen no difference between the speed of .NET-generated Eiffel# applications and that of code generated through our standard compiler (which produces machine code through C). The tradeoffs are clear: Java's interpreter-oriented design (where JIT compilers are an afterthought) has fostered portability; .NET's design has put run-time performance at the top of the design goals.

The virtual machine provides a number of mechanisms useful to the implementers of all languages: signal handling, exception handling, security, memory management, garbage collection, debugging support. The last point is particularly interesting for object-oriented languages; although a bit apprehensive at first about the efficiency of the GC process, we were interested to discover that it uses many of the same techniques that we have applied to ISE Eiffel over the years; in particular it moves objects around, as needed to compact memory, and gives excellent performance results.

Not all applications will want to rely on these services. This is where the notion of "managed code" comes in. Code is managed if it relies on the runtime's services, unmanaged otherwise. Managed and unmanaged code can coexist; as noted above, C++ will only let you enjoy the benefits of full managed code if you limit your use of the language to a subset that roughly corresponds to a C#/Java style of programming (no multiple inheritance except from interfaces, no "friends").

Language interoperability

The architecture as described so far addresses one of the central issues of component-based development: how to let components interoperate regardless of the languages they've been written in. As long as every language involved can map to a common object model both ways - producing output that conforms to that model, and consuming any conforming components - component developers and application programmers can use the languages that best fit their individual needs.

There have been multi-language component mechanisms before, notably CORBA and COM. But they imply a major hurdle: for every component that you make available to the rest of the world you must write an interface description in the appropriate IDL (Interface Definition Language). I discussed this issue in my July column, stating that in my opinion the notion of IDL is doomed - more precisely, the notion of IDL as something for humans to write. It has always been striking that the notion of an IDL compiler, which starts from an IDL interface and generates a stub in a programming language, is the wrong way around: you want to write your code and have the interface be produced automatically from it (as with the Eiffel class abstracter). I couldn't write more at the time, but .NET was very prominent in the background to my comments. With .NET there is no IDL: you just use classes from other languages as if they were from your own. The level of interoperability is unprecedented: your classes can not only be clients of other .NET classes, whatever their language of origin, but also inherit from them. There is no need to write any IDL or other glue code. On the road to reuse and component-based development, it's difficult to think of any comparable milestone in the recent past.

What this means for both component developers and component users is a dramatic simplification of the requirements put on any single development environment. You don't need to have, in every environment, libraries addressing every single application area. You provide components in your domains of expertise, where you can really bring added value. Where good libraries already exist, you benefit from them at no extra cost.

The tools also come into play. Visual Studio, in its next version, will no longer be limited to Microsoft compilers; some third-party tools will plug into it. We have already demonstrated (at the Microsoft Professional Developers Conference and at TOOLS) ISE Eiffel running in Visual Studio. If there is anything more stunning than a class from one language inheriting from a class in another, it's a debugging session that moves seamlessly from a class in one language to a class in another. Our demo showed this between Eiffel and C#; Basim Kadhim from Fujitsu Software showed similar mechanisms between C# and Cobol. (Cobol and Eiffel were indeed the two languages featured during the Bill Gates keynote that introduced the technology at the PDC. We like to think it's a way of acknowledging the past and preparing for the future!)

All this is very different from the Java approach, "use my language or die". Only three years ago, Scott McNealy wrote "Think Java. Write new applications in Java. Rewrite legacy apps with Java. Don't upgrade or downgrade. Sidegrade instead to a Java desktop device... I don't understand why anybody would be programming in anything other than Java" (in Open Finance, a Sun publication, Spring 1997). I'm not sure anyone would still dare speak like that today. .NET recognizes that the world is multi-lingual, especially the world of component-based development, and that the duty of a component model is to help interoperability, not force a language corset onto everyone.

Documenting components

One of the critical questions in component-based development is how to document the components you produce. In my last column and many other writings I have argued for the self-documentation principle and single product principle, stating that we should avoid separating the documentation from the software, and include every relevant information in each component, so that we can rely on software tools to extract the documentation, or rather the "documentations": views of the component suitable for different purposes, such as full source, signatures (equivalent to C++ header files or IDL interfaces), contracts in the Eiffel style, inheritance structures, graphical representations. This principle has always been central to the Eiffel way of analysis, design and programming, where classes are equipped with enough information to support all these views and others. It's the reverse of the idea that you write your component and then you write the IDL spec.

.NET doesn't go quite as far as Eiffel in this direction (in particular because of the absence of contracts), but uses the same principles to equip components with metadata. To compile a component in the .NET environment is not just to produce MSIL code for the common language runtime. Together with this code, a compiler for managed code will generate information that identifies the component and all the relevant type properties, including the signatures of all supported operations. The comment in my last column that "IDLs as we know them are doomed" couldn't at the time be more specific (this was written in May, before .NET was made public), but was based in part on the observation that metadata are the replacement. Metadata has all the information of an IDL interface, but it is produced by the compiler out of the component's own properties. There is no extra work for the developer.

From these observations also follows the answer to a question raised by Clemens Szyperski regarding my comments on the demise of IDLs. This is all well and good, said Clemens, but in the absence of an IDL how do we explain to a programmer working with a certain language the properties of a component from another language? Don't we need an IDL anyway, to serve as common description language? In fact with metadata we don't, at least not in the sense of a human-readable format. Appropriate tools can interpret the metadata for a component and produce an interface specification that conforms to the syntax of the target programming language. This is what we have done for Eiffel#: a tool called the emitter takes a .NET component and produces an Eiffel# class that represents the component. It doesn't matter where the component comes from: if you are an Eiffel programmer you will see it in exactly the same way as if it had originally been written in Eiffel, even if in fact it comes from a C# or Visual Basic library. You don't need to learn any Esperanto; you can just talk to the world of components in your own native language.

Metadata open many other possibilities. An application that we have already produced is a Contract Wizard which, given any .NET component, lets you equip it with contracts. It's not interesting for Eiffel, since the normal approach is to write the contracts in the code; but for other languages the Contract Wizard presents you interactively with the successive methods of a class and lets you add preconditions, postconditions and class invariants. The beauty of it is that you don't need to have access to the source code: this all works on the compiled component, generating a new version of the component, equipped with new metadata, after the code generation process.

The notion of component metadata has some other interesting consequences which no one, as far as I know, has fully measured yet. As noted, it's possible with a tool like the Eiffel emitter to process a compiled component and find out a lot of its internal information. There have been disassemblers before; what's new here is that the model is object-oriented, so you can get not only the algorithm but the data structures as well. Remember the title of Niklaus Wirth's old book: Algorithms + Data Structures = Programs. If you have both, not much is left to discover. In previous exchanges with Clemens in this column, I argued that the distinction between "source" and "binary" is becoming increasingly elusive; the availability of MSIL code with metadata is blurring the picture even more.

Levels of compliance

The basic notion of "being a .NET language" means that your compiler generates MSIL code. But beyond that there are different levels of compliance - different ways of being a good citizen in the community.

We have already seen one dimension: managed versus unmanaged. The unmanaged form serves as a bridge to approaches that cannot or do not want to benefit from the services of the common language runtime, for example if they cannot support garbage collection. It's clearly desirable, especially for an O-O language, to generate managed code. That's what Eiffel does.

Another criterion is code verifiability. Verifiable code is guaranteed not to cause security breaches. .NET's approach to verifiability raises some delicate questions when assessed against the software engineering goals of object technology, in particular as regards the variance of routine arguments and results. No one wants to open the way to the O-O equivalent of buffer overflows. Producing verifiable code for a statically typed language with a flexible type system is a challenge, but worth tackling, as we have done with Eiffel.

A particularly important aspect of compliance is the CLS level. CLS stands for Common Language System; it's the specification of three subsets of the Virtual Object System, adherence to which will ensure full language interoperability. The subsets are:

  • Compliant producer: this ensures that your components (by avoiding non-universal mechanisms) can be used by anyone.
  • Consumer: if your compiler satisfies this, your classes can reuse, as clients, components written in any compliant-producer language.
  • Extender: if your compiler satisfies this, your classes can extend classes from any compliant producer language, that is to say, inherit from these classes and redefine (override) their operations.

The first requirement directs a compiler to generate code that uses no more than the specified mechanisms. The other two require it to accept no less than certain mechanisms useful across a whole range of languages. These can be tough requirements. For example, overloading is part of the CLS at the consumer level; this means that a language without overloading (enforcing the simple rule that, within the context of a class, an operation name denotes one and only one operation) must be able to use and inherit components from other languages that define overloaded operations. In Eiffel# we have resolved the issue through a clearly specified demangling algorithm: for example if you are inheriting from a C++ class with two routines called foo, the first one will be known as foo and the second one as something like foo_INTEGER_REAL, with a name built from the signature. We wanted Eiffel to be a full citizen of the community, so Eiffel# is indeed compliant at all three levels.

Never quite the same

There is much more to .NET than suggested by this overview. I haven't covered at all the Web aspects, although you can see some of the Web services in our online demo (see the sidebar). Another major contribution is the .NET approach to configuration management, based on a strong notion of version and removing, once and for all, the perils of "DLL hell". I have only mentioned in passing the focus on security, supported throughout the environment by the use of strong cryptography. (It's clear that the Microsoft folks have seen one time too many Scott McNealy showing in public an Active X control that takes over a remote machine. As a result they have gone the extra mile on security.) I've barely touched on Visual Studio and all the new tools. I haven't talked about ADO.NET, providing a standardized interface to databases and integration in Internet applications. Many other APIs are affected by the technology; for example if you look at our online demo you will see how the same Eiffel# application can run both in a browser and in a plain client-server mode using Windows graphics.

I hope to have shown enough, however, to substantiate the main theme of this article: that no one (with the possible exception of columnists for weekly magazines) can afford to ignore .NET. The component scene will never be the same.

Acknowledgment

Eiffel# is primarily the result of the work of Raphael Simon and Emmanuel Stapf of ISE, with the help of Christine Mingins of Monash University, who was also instrumental in the design of the Contract Wizard, built by Chee Yeen Chan.

A multi-language example using .NET and ASP.NET

To see an example application using some of the .NET mechanisms, including language interoperability, Eiffel#, C# and ASP.NET active server pages, go to http://www.dotnet.eiffel.com (This site is temporarily unavailable). You will be invited to enter a fictitious registration for a TOOLS conference, giving you immediate access to the database of recent "registrants", as if you were a conference organizer. The whole source code is available for downloading, giving a good example of a practical use of .NET and ASP.NET mechanisms for a small e-commerce application.