Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

The Significance of .NET

, November 01, 2000


November 2000: The Significance of dot-NET

Many of the first reactions to Microsoft’s July announcement of its "dot-NET" (while Microsoft writes this as .NET, Software Development editorial policy is to spell it out for clarity) framework were condescending and dismissive. Bob Metcalfe mocked dot-NET in his June 30th column in InfoWorld as "a ton of vaporware that even Microsoft does not expect to ship for years." Having pledged to eat his December 4, 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 dot-NET fundamentally changes software development. In fact, dot-NET directly bears on many of the issues we have debated in Beyond Objects over the past year: the true nature of components, their relation to the classes and objects of object-oriented technology, how to document components, the role of interface definition languages, and how to combine components from different languages. The birth of dot-NET doesn’t mean you have to like everything in it (and I disagree 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.

The Architecture

At the center of the dot-NET framework is an object model, called the Virtual Object System (VOS), and at center of the object model is a type system. This already sets dot-NET apart from the other available component models, which are organized around a programming language (Java), an application interconnection model (CORBA) and a wiring model (COM). An OO enthusiast like me is likely to see dot-NET as the latest vindication of the observation that today there is no way to escape using the OO model as a foundation for serious software engineering. For all of Clemens Szyperski’s insistence that components go "beyond objects," what we end up with in the most recent component model is a notion of a component based directly on classes.

What exactly is this object model? If you know C++, Java or Eiffel, you will immediately recognize the basic concepts of class, inheritance, dynamic binding, class-based typing and so on. The object model is not, however, identical to the model of any of these languages. Rather, it’s an attempt to define a suitable base that bridges all these languages and others.

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

Most importantly, this model is designed to be language-independent. Press reports have devoted considerable attention to the new C# (C-sharp) language introduced with dot-NET, but C# is a means rather than an end. It’s a human-usable language that directly reflects the dot-NET object model–no less and no more. C# is a credible competitor to Java (to the point that DevelopMentor’s Don Box, in his keynote at TOOLS USA, quipped that everyone outside of Microsoft should call it "Java 3"), and it will undoubtedly attract a significant subset of the Java programmer community. However, Microsoft isn't on a crusade to convert the world to a new programming language; the focus is in on the model. The dot-NET framework itself is language-independent and attempts to provide a reasonable target to which all current languages can map. It’s clear that one of the internal incentives for designing the framework was the decision to avoid duplication of efforts within Microsoft by the development groups for the various "Microsoft languages," including Visual C++, Visual Basic, Jscript and what was to become C#. The framework enables compilers for all of these languages to share a common back end, and Microsoft architects showed real vision when they opened up this back end to other languages.

My company, ISE, was privileged to be one of the outside language providers selected; more than a year before the first official announcement of dot-NET, ISE started to port its technology to the new platform, in collaboration with Microsoft. This enabled us to announce Eiffel-sharp (Eiffel#) at the same time as the framework’s announcement. Other third-party languages being ported to dot-NET include Cobol, APL, Perl, Python and Smalltalk, as well as research languages such as Haskell, ML, Oberon, Scheme and Mercury.

More on the Object Model

My reservations about some aspects of the dot-NET model mostly follow from the observation that some decisions perpetuate misguided choices of the dominant C-to-C++ -to-Java line. It’s not surprising that the designers of the framework should have retained a high degree of compatibility with these languages due to the legions of programmers who use them, but that doesn’t make these choices right. Examples of these regrettable decisions include the presence of overloading, a vanity mechanism that brings nothing to the semantic power of an OO language, but hampers readability and complicates everyone’s task. There is also no support for multiple inheritance except in the case of Java-style interfaces. The C++ mechanism for constructors (based on using a single name for all constructors for a class) disambiguates clashes through an irrelevant property, type signatures, and forces the initialization of an object to go through the initialization procedures for all ancestor types. There is still a novariant policy for routine arguments and results (when you redefine a routine, you cannot change the types of its arguments), and it’s difficult to keep a clear separation between member names and member values (to support, for example, renaming as in Eiffel). I understand the dot-NET designers are not responsible for C++. However, these decisions, force a complicated solution where simple ones are readily available.

Some of these are matters of taste. With Eiffel, the only major problem is multiple inheritance. Not surprisingly, C++ has the same problem; as a result, the current dot-NET implementation of C++ permits multiple inheritance only in "unmanaged" mode. We want to generate managed code, so we have had to devise rather intricate solutions. As anyone who has observed Java and Smalltalk programmers knows, removing multiple inheritance from an OO language leads programmers to emulate it manually through tricks that shouldn’t be described in a family-oriented column. By the way, ignore anyone who tells you that multiple inheritance is inherently bad, dangerous or messy. There is only one reason Java and dot-NET don’t have multiple inheritance: It makes dynamic class loading far more difficult to implement.

These disagreements notwithstanding, the object model’s power and versatility has enabled us, as well as the teams for numerous other languages, to port our compilers to dot-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 dot-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 dot-NET framework. So Eiffel# does support generic classes, and the full extent of contract mechanisms–class invariants, preconditions and postconditions. This is one of the strengths of the dot-NET approach: It provides the basis to implement a number of different approaches to software engineering.

The Virtual Machine

The Common Language Runtime (CLR) is the basis for the dot-NET virtual machine. Of course, this isn’t 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 CLR is able to execute a specific instruction set, known as Microsoft Intermediate Language (MSIL), the direct embodiment of the VOS object model. Porting a compiler to dot-NET means, among other things, retargeting the compiler to generate MSIL instead of, or along with, machine code or another intermediate language. A distinctive trait 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 "JIT-ting the code," where JIT stands for Just In Time compilation. Here dot-NET draws on the lessons of the Java experience (familiar to anyone who has ever seen the dreaded message "Starting Java" appear 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 dot-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; dot-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 and debugging support. The last point is particularly interesting for object-oriented languages; although we were apprehensive at first about the efficiency of the GC process, we discovered that it uses many of the same techniques that we have applied to ISE Eiffel. In particular, the GC process moves objects around as needed to compact memory, resulting in excellent performance.

Every application won’t rely on these services. This is where the notion of "managed code" comes in. Code is managed if it relies on the run time’s services, unmanaged otherwise. Managed and unmanaged code can coexist. However, 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# or Java style of programming (no multiple inheritance except from interfaces).

Language Interoperability

The architecture as described so far addresses a central issue 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 by both producing output that conforms to that mode and consuming any conforming components, component developers and application programmers can use the languages that best fit their needs.

Multilanguage component mechanisms have existed before, notably CORBA and COM. But they contain a major hurdle–you must write an interface description in the appropriate interface definition language (IDL) for every component that you make available to the world. As I wrote in "Contracts for Components" (Beyond Objects, July 2000), the notion of IDLs, viewed as something that humans must write, is doomed. An IDL compiler, which starts from an IDL interface and generates a stub in a programming language, operates the wrong way around. Instead, you should write your code and have the interface be produced automatically from it (as with the Eiffel class abstracter). There is no IDL with dot-NET: You just use classes from other languages as if they were from your own. The level of interoperability is unprecedented. Your classes cannot only be clients of other dot-NET classes, whatever their language of origin, but can also inherit from them. There is no need to write any IDL or other glue code–a tremendous boon to reuse.

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 libraries addressing every 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.

Tools also come into play. Visual Studio's next version will no longer be limited to Microsoft compilers; ISE has already demonstrated Eiffel running in Visual Studio at the Microsoft Professional Developers Conference and at TOOLS. 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. Basim Kadhim, Fujitsu Software’s Chief COBOL Architect, showed similar mechanisms between C# and COBOL at the PDC.

All this is very different from the Java approach. Only three years ago, Scott McNealy wrote in Open Finance, a Sun publication, "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." I’m not sure anyone would still dare say that today. Dot-NET recognizes that the world is multilingual, 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

A critical questions in component-based development is how to document the components you produce. In my last column and elsewhere, I have argued for the "Self-Documentation Principle" and "Single Product Principle," which state that we should avoid separating the documentation from the software; instead include all relevant information in each component. Doing so allows you to rely on software tools to extract the documentation, in more than one sense. You can see 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 and 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. Dot-NET doesn’t go as far as Eiffel in this direction (primarily because of the absence of contracts), but it uses the same principles to equip components with meta-data. To compile a component in the dot-NET environment is not just to produce MSIL code for the CLR. 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. I couldn't be more specific when I wrote in July that "IDLs as we know them are doomed," but my comment was based in part on the observation that meta-data is the replacement. Meta-data 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.

To paraphrase Clemens Szyperski: This is all well and good, 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? ("Components and Architecture," Beyond Objects, Oct. 2000). In fact, we don’t with meta-data, at least not in the sense of a human-readable format. Appropriate tools can interpret the meta-data 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 dot-NET component and produces an Eiffel# class that represents the component. It doesn’t matter where the component comes from; if you program in Eiffel, you will see the component exactly the same way as if it had originally been written in Eiffel, even if it comes from a C# or Visual Basic library. You don’t need to learn Esperanto; you can just talk to the world of components in your native language.

Meta-data opens other possibilities. We've already produced a Contract Wizard, which allows you to equip any given dot-NET component with contracts. This feature isn’t interesting for Eiffel, since you would normally write the contracts in the code. But for other languages the Contract Wizard interactively presents you with the successive methods of a class and lets you add preconditions, postconditions and class invariants. You don’t even need access to the source code–it all works on the compiled component, generating a new version of the component, equipped with new meta-data, after the code generation process.

The notion of component meta-data has some other interesting consequences, which no one, as far as I know, has fully measured. As noted, it’s possible with a tool like the Eiffel emitter to process a compiled component and find a lot of its internal information. There have been disassemblers before; what is 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 book, Algorithms + Data Structures = Programs (Prentice Hall, 1976). If you have both, not much is left to discover. In previous exchanges with Szyperski in this column, I argued that the distinction between "source" and "binary" is becoming increasingly elusive; the availability of MSIL code with meta-data blurs the picture even more.

Levels of Compliance

Being a dot-NET language means that your compiler generates Microsoft Intermediate Language (MSIL) code. But beyond that there are different levels of compliance.

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 can't support garbage collection. It’s clearly desirable, especially for an OO language, to generate managed code. That is what Eiffel does.

Another criterion is code verifiability. Verifiable code is guaranteed not to cause security breaches. Dot-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 OO equivalent of buffer overflows. Producing verifiable code for a statically typed language with a flexible type system is a challenge, but worth tackling.

A particularly important aspect of compliance is the Common Language System (CLS) level. CLS is the specification of three subsets of the Virtual Object System, adherence to which will ensure full language interoperability. The subsets are:

  • Compliant producer (also called "framework level"): this ensures that your components, by avoiding nonuniversal 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 dot-NET than suggested by this overview. I haven’t covered the Web aspects, although you can see some of the Web services in our online demo (see the sidebar). Another major contribution is dot-NET’s approach to configuration management, based on a strong notion of version, which removes, once and for all, the perils of "DLL hell." I have only briefly mentioned the focus on security, supported throughout the environment by the use of strong cryptography. I’ve barely touched on Visual Studio and all the new tools. I haven’t discussed ADO+, which provides a standardized interface to databases and integration in Internet applications. Many other APIs are affected by the technology; for example, our online demonstration shows how the same Eiffel# application can run in both a browser and a plain client/server mode using Windows graphics.

I hope to have shown enough, however, to substantiate my thesis: No one can afford to ignore dot-NET. The component scene has changed forever.

Acknowledgment

Eiffel# is primarily the result of the work of Raphael Simon and Emmanuel Stapf of Interactive Software Engineering Inc. in Goleta, Calif., 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 dot-NET and ASP+

To see a sample application that uses some of the dot-NET mechanisms including language interoperability, Eiffel#, C# and ASP+ (Active Server Pages+), go to http://www.dotnet.eiffel.com. The demonstration program is a conference registration system. It invites you to register to attend a conference, giving you immediate access to the database of recent "registrants." You can download all the source code, which provides a good example of a practical use of dot-NET and ASP+ mechanisms for a small e-commerce application, combined in this case with Eiffel’s Design by Contract tools

At the heart of the example is a Web service, the dot-NET name for a set of facilities available both to humans, through a Web browser, and to programs. One of the principal classes is REGISTRAR, which starts like this:

indexing

description: "Registration services: add registrants and registrations."

class

REGISTRAR

alias

"RegistrationService.Registrar"

inherit

WEB_SERVICE

create

start

feature

[The features of the class follow]

This class inherits from WEB_SERVICE, which encapsulates the notion of Web service. Note the alias clause, giving the external name of the the class in the global dot-Net namespace; this is the name under which other languages may refer to it, after possible conversion to their own naming conventions.

Class WEB_SERVICE itself is a direct encapsulation of the corresponding dot-NET class, which is available as part of a DLL. We don’t have the source code (probably written in C#) but that doesn’t matter, since we can see its methods through the MSIL (Intermediate Language) disassembler.

Figure 1.

On the Eiffel side, WEB_SERVICE is an external class, a mere wrapper, which starts like this:

external class

WEB_SERVICE

alias

"System.Web.Services.WebService"

inherit

COMPONENT

create

make_webservice

feature -- Access

frozen server: HTTP_SERVER_UTILITY is

external

"IL signature : System.Web.HttpServerUtility %

%use System.Web.Services.WebService"

alias

"get_Server"

end

… Other features omitted …

This class text is automatically generated by the Eiffel "emitter" from the description found in the dot-NET DLL. It provides Eiffel classes with direct access to the corresponding class, with exactly the same performance as if it were called from C#, Cobol or another dot-NET language. The same holds for Eiffel-originated classes such as REGISTRAR, which programmers in other languages can use without having to know what language it comes from, and without incurring a performance penalty.

 

REGISTRAR as shown above offers a number of features (methods), which will be available to the outside world in two ways: through the Web page, where the ASP+ code may trigger execution of a feature as a result of a user action, and through other programs that will access the feature using the Web services API. You can try the first of these techniques by simply going to the registration page and typing a correct entry. Your (fictitious) registration will then be entered into the database; you can then put yourself in the shoes of the conference organizers and look at the record of recent registrations on another Web page.

Here is how a typical feature from the class looks (with a few ommissions–download the actual class text for the full details):

add_registrant (address_form, first_name, last_name,

company_name, address, city, state, zip, country: STRING) is

-- Add new registrant.

require

non_void_address_form: address_form /= Void

non_void_first_name: first_name /= Void

non_void_last_name: last_name /= Void

non_void_company_name: company_name /= Void

non_void_address: address /= Void

non_void_city: address /= Void

non_void_state: address /= Void

non_void_zip: address /= Void

non_void_country: address /= Void

valid_adress_form:

address_form.equals ("Mr.") or address_form.equals ("Mrs.")

or address_form.equals ("Miss") or address_form.equals ("Ms.")

or address_form.equals ("Dr.")

valid_first_name: first_name.length /= 0

valid_last_name: last_name.length /= 0

valid_address: address.length /= 0

valid_city: city.length /= 0

valid_state: state.length /= 0

valid_zip: zip.length /= 0

valid_country: country.length /= 0

local

do

create zip_verifier.make (zip)

if not zip_verifier.is_integer then

else

full_address := string.concat (address, ", ")

full_address := string.concat (full_address, city)

if new_registrant.initialized then

set_last_registrant_identifier (new_registrant.identifier)

registrants_database.store

(new_registrant, last_registrant_identifier)

set_last_operation_successful (True)

else

set_last_registrant_identifier (-1)

set_last_error_message (new_registrant.error_message)

set_last_operation_successful (False)

end

end

end

There is an extensive precondition, which states the requirements on a correct entry. If you violate the valid_address clause of the precondition, for example, you will be taken to a page stating precisely what happened.

Figure 2.

Clearly, this error page is not intended ever to be shown to a Web site's visitor, who couldn’t care less about assertions and stack traces. Indeed, in component-based development, the components that are directly accessible to human users should not have preconditions, but should instead take care of filtering invalid uses so as to meet all the preconditions of all the internal components they use. But you won’t always get this right the first time, so during the development process, the Design by Contract mechanisms–in particular the ability to turn on assertion-checking in an ASP+ page–will be invaluable. The software developer who sees the above screen during testing will immediately find out what is wrong–the page failed to check the validity of some of the data before passing them on to the processing modules–and will be able to fix the problem.

Bertrand Meyer

 


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.