Posted on 03/09/2002 8:32:06 AM PST by Vince Ferrer
(Available also as PDF)
The .NET platform is a huge step forward if compared to Microsofts previous SDKs. Still, there is a long distance from marketing to reality. Just like Win32 / MFC / COM / etc. before it, .NET will compete with existing platforms (such as Java) for the leading position in the next generation of application developments. Microsoft has clearly elected J2EE as Enemy #1 to be defeated. Comparisons to Java technology are already popping from the .NET marketing found in Microsoft(-sponsored) websites and publicity. This focus is very odd as Microsoft tried to completely ignore the existence of Java until recently (C# documents, for example, mention only C and C++ even though C# is a Java copycat with superficial changes).
One important item of debate is the language-neutral nature of the CLR (Common Language Runtime). Briefly, the CLR is .NETs competitor for the JVM, a virtual machine that runs portable bytecode known as MSIL (Microsoft Intermediary Language). The CLR is supposed to be equally friendly to any language you throw at it, and this is being sold as a huge advantage of the .NET platform: free choice of programming language. The CLR would not only support multiple languages, but also make easy to mix them: a single program could match classes written in different languages. This includes even cross-language inheritance (say, a C# class inheriting a VB base class). The CLR implementation is backed by two related specifications, the CLS (Common Language Specification) and the CTS (Common Type System) that language vendors follow to compile their code for the CLR.
The most common criticism found against cross-language capability is that it would be actually detrimental to software development. This is easy to understand by any architect or project leader. Even with a single language and set of frameworks (C++/MFC, Java/J2EE, whatever), plus a range of tools (IDE, CASE, version control), it is difficult to make everything run seamlessly. Systems are never perfectly designed and documented. People have different skillsets, experience, and tastes for design and programming. Increased developer count and turnaround means increased effort in integration, testing and maintenance, and harder reuse. Whenever developer A doesnt immediately understand (or like) some code written by developer B, the result is any choice of wasted effort, redundant development, and new bugs.
This criticism is of moderate value in our opinion. Surely, nobody wants to inherit ten thousand lines of code written in a weird language by an intern who found another place to work. Its not expected that project managers are so nice as to let people use whatever tool they like (except maybe, for research). However, managers and architects themselves will often push their favorite technologies, with little care for the companys long-term costs. Even this risk can be controlled; its a matter of choice and responsibility. We cannot really argue against choice of programming languages, as we already benefit from this choice in most environments. Its only not as seamless as promised by .NET: typically, we need bridges and middlewares for cross-language bindings. Many people see these barriers as positive: they enforce minimal interfaces, good encapsulation, and black-box reuse. But then again, maybe this should depend on developer discipline.
Unfortunately, this whole debate acknowledges .NET to be language-neutral in the first place, which is simply not true, certainly not in the extent advertised by Microsoft. The issues raised by a partially language-neutral environment are very important, so we should better know them.
While .NET's runtime tries to be language-neutral, this is true mostly for languages that look a lot like C#, which semantics, typesystem, and runtime requirements are richer than Java's but still poor from the point of view of many other languages. The C# language is even described by Microsoft as an 1-to-1 mapping of the CLR/CTS/CLSs features (exactly like Java for the JVM).
Programming languages exist in wide variety, not only because different tasks (from systems programming to artificial intelligence) require different tools, but also because there is no One True Way to serve even one domain. After half a century of research, computer scientists have yet to agree on a single answer to most issues of programming language design.
A large number of developers spend all their productive life using a few mainstream languages and regard most interesting advancements as academic toys. This is patently false when we realize that todays mainstream features are the "research toys" of twenty years ago. Objects, garbage collection, distributed systems, virtual machines, you name it and its probably not invented last week by either Sun or Microsoft, no matter what their marketing tells you.
We follow with a partial list of limitations of the CLR/CTS/CLS.
Languages such as C++ and Eiffel need multiple inheritance of implementation. Cross-language support for MI may not be possible, as MI creates some hard problems (like repeated inheritance and name clashes) that different languages solve in different, incompatible ways.
Another alternative is mix-ins (considered by many a superior alternative for MI, enabling a dynamic, loosely-coupled mechanism for behavioral reuse). This is not supported by the CLR, but required by languages such as Python.
The result: Dialects that cripple the original languages to become CLR-compatible. Examples are Managed C++ and Eiffel#, both not supporting implementation MI.
Compile-time mechanisms like C++s templates are supported, but they are not cross-language: no way to instantiate your stack<T> from C#. Instantiated templates will be accessible from other languages, but not interoperable. If a C++ stack<T> class expands to stack<Dog>, methods such as stack<Dog>::add(Dog) will be generated according to some set of rules (such as name mangling rules) that are not part of the CLS. This makes impossible for an Eiffel program to "see" this class as Eiffels STACK[DOG], unless accepting C++s rules as a de facto standard. The erasure model, used for example by Generic Java, is another challenge. Again, generic types are not made all equal; creating a cross-language standard may also prove to be impractical.
The Result: Half-baked generic type support without cross-language capability. Crippling important details of some implementations, like covariance (gone from Eiffel#). Enforcement, through the system libraries, of "poor mans generics" (use of opaque Object references).
This is not good for languages that use completely dynamic typing to avoid the complexity of static type systems. Examples are Smalltalk, LISP and their relatives. However, storing critical values such as integers, floats and booleans as full-blown dynamic-typed entities is expensive. Therefore, these languages resort to tagged memory: a few bits of every memory word are used as type IDs, to cheaply identify end encode at least the most critical objects. (A common side effect of this implementation is that integral types have odd sizes, e.g. 30 bits instead of 32 for a 4-byte integer; this is because 2 bits were taken by the tag bits.)
Techniques used in such systems are different from those used elsewhere. The major problem is that any high-performance GC needs to identify pointers precisely. In static-typed systems, the JIT compiler generates "type maps" for every class and method. The GC uses these maps to know which fields, stack positions, or registers are pointers. But this is not good enough for dynamic-typed languages, where the GC relies on tag bits to tell objects from primitives. The CLR would need to support a mix of tagged and untagged primitives in the same heap (plus references); and different dynamic languages may use different tagged memory specs.
Again, not all languages agree how to define and dispatch methods. In dynamic-typed languages, there is no way to automatically produce typed signatures for methods. The compiler could declare Object for everything, but users of other languages would hate such interfaces.
Some languages support multi-methods, where the dispatch is polymorphic on several arguments and not on a single argument (the receiver, a.k.a. this or self). For example if you may have Float and Matrix classes and four versions of "multiply (lhs, rhs)", and the correct version is selected at compile time after inspecting the type of both arguments. (Overloading emulates this behavior in many languages, but dispatch is static, so it doesnt play well with by-ref parameters. Multi-method dispatch is dynamic and it fully supports inheritance and polymorphism.)
Vtables (tables of code pointers) are not capable enough for all languages. This dispatch mechanism is great when there is static type data specifying, at least, the invoked method. For example, if youre calling "obj1.doThis()" and "doThis()" is defined by type X, you need to know at least that obj1 is an X. If you lack this information, alternative dispatch methods are required (reflection works, but not efficiently). Dynamic languages often use a model where method invocations use a selector (an internalized signature). The receivers dynamic type is combined with the selector to efficiently locate methods in hash tables maintained by the VM.
Finally, the CLR supports method pointers but not full closures (a.k.a. blocks). A method pointer associates the method address and a receiver in one variable, and its good enough for tasks such as event handlers. A closure goes one step beyond; it is bound to all variables visible at its static scope (possibly another closure). This makes possible a range of novel programming techniques (if you want to make a Smalltalker happy, ask him to explain these). Just like Javas inner classes, C# (and the CLR) chose a crude alternative to closures with the excuse of performance.
For the CLR, we can certainly rely that everything is optimized to favor C#. The result will be inferior performance for any language which behavior is significantly different from C#.
One simple example is memory management. The C# programs are likely to generate a modest load on the memory system, because the CLR supports value semantics and manual stack allocation. So C# programs will use the heap less heavily than, say, Java or Smalltalk. Programs written in other languages will probably perform less efficiently than in their "native" runtimes, mostly because they lack tuning for their needs. Notice that even VMs written for a single language (including Java) often offer multiple choices of components like GC and JIT compiler, because no single option is ideal for every application.
Once support for many languages becomes available for the CLR, people will start writing the same microbenchmarks in multiple languages and run them all on the CLR. Of course C# will look faster than everything else, but people will incorrectly assume that C# is intrinsically more efficient than all other languages, when in truth its not a level playing field. The first problem is again the type system: every language where some fundamental types cannot map directly to the CTS will need some amount of additional code, inserted by compilers, to marshall/unmarshall types at all points of possible contact with other languages (including all public methods).
Another interesting issue is the invocation stack. Most languages use a stack of invocation frames, which grows with method calls and retracts with returns (or exceptions). But some languages prefer continuations, an exquisite mechanism where methods never really return; they "continue" to a new program context that works like if the rest of the program was inserted at the return point. Implementation is very different, because continuation frames cannot be allocated in a stack (but in a heap) and this model impacts compiler optimizations heavily. The CLR has no support for this; a continuation-based language (like Python) can be implemented (just like it is in Java), but performance will certainly suffer, and cross-language features probably will, too.
Example for Syntax: identifiers. CLS identifiers cannot be disambiguated by letter case alone. This is incompatible with the behavior of most case-sensitive languages. Conversely, the CLR supports overloading, requiring some mapping for languages not supporting this feature.
Example for Runtime System: A few languages allow objects or classes to change type or structure at any time. The CLR makes no attempt to allow these features.
Example for Object Model: Not all OO languages are class-based. In prototype-based languages (Self, Javascript), there are no classes; only objects (or: every object is its own class). A new object is defined by cloning other object and inserting fields or methods.
In some cases the languages can be modified to become CLS-compatible without big disruption. In other cases, advanced features are not widely used even by most application code of that language; some features are usually employed by implementation of system-level support (say, middlewares or debuggers) and alternative implementations are possible in the .NET platform. However, there are scenarios where even the most advanced facilities are absolutely required; good examples are languages with mix-ins and prototypes: in both cases, dynamic construction and mutation of classes is required to be available and efficient.
If you are a Smalltalk programmer, you cannot invoke methods that take blocks as arguments and apply them to the elements. If you are a C++ programmer, you cannot do all those neat template tricks. If you are a LISP programmer, you won't see a mess of atoms tied together like a Christmas tree. For the Eiffel programmer, no multiple implementation inheritance, and so on. Whatever the language, you will do things the C# way, at least in every point of contact with the external world (a.k.a. "everything not written in your language" or "any exported interfaces").
We have some experience of this phenomena with distributed services (CORBA, COM) and Web Services; but these usually enforce coarse-grained interfaces, and the native languages frameworks and techniques are used most of the time. For example, a Java programmer will use Javas collections for all data structure work, and CORBA sequences only for walking the IIOP highway. However, if you want your program to be a first-class citizen in the .NET universe, you are bound to use its libraries for collections, GUI, streams, networking, threading and son on.
This is not different from the JVM imposing the J2SE / J2EE APIs; which is not a problem for Java programmers because they are using the Java language, so they are using frameworks that perfectly match the language. The same factor benefits C#, that adopts the new .NET libs as its native framework. .NET programs written in any other language will be bigger and more complex than C# equivalents, if they insist using their native (Microsoft will probably label "legacy") libs, and need to convert things to and from the .NET types. Pick the framework that is suited to your (non-C#-clone) language, or cross language programming, but not both.
This page contains an excellent overview of Eiffel#, a new dialect of Eiffel created to overcome many limitations of the CLR (and this is for a close relative of the C/C++/C# family):
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/pdc_eiffel.asp/
One major new issue in this article is support for Design-by-Contract, a fundamental strength of Eiffel that .NET does not support. This will never change, because DbC forces developers to a discipline that easily explains the (un)popularity of Eiffel, while Microsoft makes SDKs for the masses. So Eiffel# needs workarounds such as wrapping .NETs libraries to add contracts.
Smallscript is another language tweaked for .NET, in this case with Smalltalk roots. Check the public newsgroup: for some interesting threads, like "Fun with Reflection / What Should be in the Core-release?", where a Smallscripts leading scientist David Simmons is argued on limitations imposed by the .NET architecture:
> From my understanding, due to .NET VM limitations, SmallScript has limited > dynamic features on that platform, but the full dynamic Smalltalk features > are provided on the AOS platform.
Basically true. Let me try to re-phrase the issue.
The .NET VM is designed for statically typed and compiled languages. SmallScript LLC has developed technologies to efficiently support execution of dynamic typed and bound languages [scripting languages included]. The "interactive" aspect of languages such as Smalltalk is too difficult a problem, in current .NET architecture, to address/provide with the "classic" Smalltalk IDE experience. (...)
> Can we expect to be able to do things like add classes while > debugging on the .NET platform?
No. Microsoft design decisions and their development time schedule constraints in this version resulted in cutting any real mechanisms for edit-and-continue style behavior.
It can be done, but what one has to do under the hood is very painful and grossly unneccessary/complicated. Which is to say that it won't be done by us for this version of .NET unless the commercial rewards justify it.
Since .NET is badly fitted to ST, they tweak a Smalltalk-friendly virtual machine (AOS) to work cooperatively with the CLRs VM and binary standards. The same code can be compiled for either AOS or .NETs CLR binaries, but the .NET version will not support some features. This is despite the fact Smallscript code is sometimes hard to recognize as Smalltalk (its a heavily extended dialect), and at least in the current betas demos, theres no use of any of .NETs class libraries (Win32 low-level APIs are invoked instead: back to window message processing).
A big irony is that the AOS VM is apparently superior to the CLR in multi-language support, so Smallscript LLC even plans to support many other languages like Python and Scheme.
Java advocates have long pointed that the JVM doesnt force users to the Java language. The JVM supports many languages (http://flp.cs.tu-berlin.de/~tolk/vmlanguages.html/) even without the enhancements considered necessary to make it as neutral as the CLR. Critics argue that few of these languages became hits (JESS, AspectJ, Jython and GJ are exceptions, with respectable popularity.) But performance and even cross-language integration (many languages in the list have seamless integration with Java) are not the reason why most people just kept using Java.
More recently, Halcyons Instant Net (http://www.halcyonsoft.com/) adds spice to the debate: this product translates .NET executables (MSIL code) into Java! All the translated code requires is the JRE and a 3Mb .jar which implements the .NET APIs on top of the Java framework. The product goes as far as supporting ASP.NET, WinForms and VisualStudio.NET, so you can write your classes, pages and web services in C#, VB.NET or Eiffel# (or even the J# abomination) and deploy to some J2EE server of your preference. Its still early to tell if Halcyon has the resources to catch up with .NETs evolution, or if the new layers of frameworks will have big tradeoffs in performance and other concerns, and I doubt iNet will support unmanaged code (the only kind of .NET code that cannot be easily translated to JVM-compatible bytecode). Even so, the existence of this product (which initial beta runs successfully many .NET programs) speaks volumes of the superiority of the CLR over the JVM in language neutrality. Not to mention that its runtimes small size, notwithstanding the yet-incomplete .NET API support, makes it look like .NET is such a straightforward Java clone that such a relatively thin Java wrapper can emulate it.
The Common Language Runtime is being sold as a libertarian technology that levels the playing field for minority languages. The CLR would offer to all languages a neutral typesystem, a state-of-the-art back-end compiler, runtime and set of enterprise-class frameworks. VisualStudio.NET makes this complete with a first-rate IDE that can be extended to support any language. It would almost zero the barrier to entry for new languages.
The reality looks much darker instead. The CLR is not truly language-neutral, and it will ostensibly favor languages that look a lot like C#. Those not in this group will be severely bastardized, producing dialects which are really "C# with another syntax"; look at ISEs Eiffel# (or even Microsofts own VB.NET and J#) for great examples. Programmers choice will be limited to superficial features: whether to delimit their blocks with curly braces, Begin/End or parentheses. Its also worth notice that the CTS/CTS do not allow use of the full set of CLR features; for example, unsigned integers are supported by the CLR but not considered language-neutral, simply because many languages share Javas abomination for the signed/unsigned duality (this includes Microsofts own VB) and theres no good solution for this issue.
This scenario will severely block innovation in the programming language field because a whole generation of programmers may be educated by CLR-compatible languages, and what they will learn is that all languages are identical. They will know only one model for inheritance, typing, frameworks, etc. They will be much less likely to experiment with truly different alternatives. Languages that are not neatly integrated to the CLR (and VisualStudio.NET) will face a higher barrier to entry than languages without an easy, RAD-type IDE have these days. Life will also be difficult for libraries that dont closely follow .NETs blueprints, including any library that is designed for portability (portable code can hardly depend on .NET specifics).
A very significant part of the problem is the closed nature of the CLR. Microsoft brags loudly about the ECMA C#/CLS standard, but will keep absolute control of all frameworks and implementations. Vendors of minor languages cannot customize any part of the CLR to better support something thats not C# in disguise. These vendors will fail to make as many converts as they expect; virtually all .NET programmers will use C#, VB.NET or C++ (and a whole new article can be written on the issues of unmanaged languages). I believe the same fifty people will keep hacking in languages like SML or Dylan, and the vendors advantage will come mostly in reduced cost as they wont worry with compiler back-ends, runtimes, IDEs or libraries.
Playing with the .NET SDK, the cross-language support looks impressive, but the illusion holds true only until realizing that all languages in the mix are virtually identical. Microsoft has actually invented the concept of skinnable language: changing a languages most superficial aspects, and claiming the result to be a new language. There is only One True Language that is C#, and "skins" offered by Microsoft and third parties. Just like in GUIs, these skins will alter the systems look and feel, add a few features, but never compete with a fully new toolkit.
There are, actually, many successful "common language runtimes", with names like Pentium, SPARC and others. Mainstream CPUs are equally fitted to very different languages as they only do the most fundamental, low-level operations, so they cannot be biased towards particular languages. There arent many different ways to perform a conditional branch. However, there are radically different ways to support methods and functions, or most constructs found in high-level languages. The consequence is that every language needs different compilers and runtimes to implement their features, and different libraries to support their vision of software development.
Microsoft is selling a concept that cannot be perfectly implemented, and for which an imperfect implementation might easily do more harm than good. How does Java compare, anyway? The JVM is even less language-neutral, but at least Sun doesnt claim it is language-neutral. Even if CLRs level of language neutrality becomes important, this can be patched into the JVM without major effort. A surprisingly large number of issues can be fixed by specification alone (for example, how to construct enumerated types? classes can be used, but different approaches are possible). Others, like unsigned integral types, would require changes to the VM and bytecode specifications. Unmanaged code is the only radical departure from the current JVM, but were not likely to want the big tradeoff in robustness and security. There is already a specification for language-neutral debugging (http://www.jcp.org/jsr/detail/45.jsp); if .NETs level of language neutrality ever becomes an important competitive feature, it wont be very hard to add many other enhancements to the Java runtimes and specifications.
Devotees of obscure languages who bought Microsofts vision of heaven will learn the issues exposed here the hard way. The most successful alternative languages, by the way, are scripting languages, in both platforms. I see many people using tools like Jython or Jruby with the JVM, or Smallscript with .NET, because both Java and C# are not ideally suited to scripting tasks such as writing dynamic page layouts. Niche work will also welcome custom languages (JESS, an expert system engine for the JVM, is a great example). When it comes to general-purpose development, though, competing languages have little chance.
All the above is not meant to say that .NET is not a good platform, the CLR is not a good architecture, or C# is not a good language. Personal preference and the test of time will decide if .NET is a good platform. Right now it seems like a very impressive piece of engineering, and most important a much needed replacement for Microsofts current platform, but unfortunately still bound to Microsofts usual way of doing (and marketing) things: proprietary technology presented as the apex of openness, and a strongly biased system presented as language-neutral.
Osvaldo Pinali Doederlein, February 2002
Language neutrality in a virtual machine, where the languages are similar, already have been done, and apparently better than .net. But if you are not going to fully implement each language, anything you run must be converted to the new .net version of the language. This negates the time saved to convert existing code to .net, because you either have to convert to C#, or heavily modify the existing code to match the mutant languages.
C# was created by Anders Hejlsberg who in 1982 created Turbo Pascal and 1994 created Delphi. C# is just Delphi with {} pairs instead of begin and end; pairs. It uses the term NameSpace to mean what Unit means in Delphi. In C# the get and set method names for properties are pre defined. They are not in Delphi.
C# has better memory management and garbage collection. But the dot Net library looks a lot like Borland's VCL in design and implementaion. That is not strange since the same guy designed and implemented them both.
Various things in Delphi are the same in C# but have different names. Obviously using the same identifiers in C# as in Delphi would invite a suit from Borland. But C# is very much what Delphi would be if Anders had stayed with Borland. It is obvious why Bill Gates paid what he paid to get Anders.
I wrote part of the code for some of the VCL components in Delphi 6 and Kylix and I learned C# in less than an afternoon. It was nothing like learning a new language. It was mostly like leaning an upgrade of Delphi.
The SUN people try to tell everyone that C# is Java with minor changes. What Microsoft is working on is a language called J# (JAY SHARP) which will be out this fall.
I am a big Smalltalker, but compiled C certaily has its place too. Smalltalk plus a wise use of C is the best of both worlds in my experience. I assume Microsoft is too smart to come out with C for .net.
That is just complete nonsense.
Directly compiled code would not speed up a dot net application.
The dot net library is very high level. One pcode call often implements thousands of machine instructions. Many if not most of the calls implement tens of thousands of machine instructions. If one pcode call that takes 5 machine cycles, is then followed by a thousand instructions at machine speed, the pcode and the function it calls takes 1005 machine cycles to run. Writing it in a complied language would reduce the execution time of this example by 3 or 4 machine cycles. With processers running at 1,000,000,000 cycles per second, 3 or 4 cycles out of a thousdands is insignificant. In an application the difference is almost ummeasureable
We are approaching the limits of the xx86 processor instruction set. With in a few years we well have new processors with instuction sets beyond anything currently available. Operating system calls will be moved to the processor instruction set for speed. To maintain operating system continuitiy it will be necessary to have an operating system, its development languages, and compilers independent of the instruction sets of the processor upon which it is running. Otherwise all applications will be obsolete and have to be replaced in one fell swoop.
To have a smooth and gradual transition to the new processors down the road the operating system must be machine code independant. That Requires p-code.
Good question, and I don't know thae answer. My reason for my comment is that when I use C, it is to call a C function from Smalltalk. I use Smalltalk 90% of the time, but when I need optimization for speed, compiled C is available for me. If Microsoft C will be built on .net, I am simply going from one virtual machine to another, and probably won't get any speed advantage at all. I can't imagine that C code running in a .net VM would be as fast as straight compiled C. Can the compiler compile to .net bytecodes and to executable code? Is Microsoft is going to compile their office apps and OS to run on the .net vm?
Do you know if C++ in Visual Studio is a full implementation of C++, which the article is not true?
There are many things about .NET that have become a joke outside the MS cloud, and 'multi-language' projects is one of them.
From an architect's point of view, multi-language makes sense when one language can do things others can't. Any other time, using multiple languages to do the *same* thing creates serious headaches and nightmares. Just ask any web page designer trying to keep straight HTML/DHTML/JavaScript/ASP/Flash/etc.
The vastly superior development model is to standardize on on language for each 'tier', uniquely suited it it's needs:
The next step is to 'web applications'. Sometime in the next 5 - 10 years, web sites will become fully interative programs.
The only current contender in this space is the Java applet. I believe MS will come out with something to compete, adding into .NET an 'applet' type capability. But they will take years to get it right. So for the next 5 years minimum, Java is going to be an 'overnight success' on the desktop, as more and more people see what modern 'swing' applets can do.
If you're one of the people who's never seen one,
Warning: if you've never installed Java 1.4, there is a one-time download that on a cable-modem takes about 5 mins.
You can also install Java 1.4 yourself, in any of various other ways, bypassing this download.
Click around on that, see what an applet can do and what web sites will become.
Now don't get me wrong, if you're using MS products and languages now, then for web development .NET is a *major* leap forward.
But if you're using Java, .NET has a loooooong way to go to even compete.
If you look at C# and then Java and *don't* see that C# is a copycat, then you're not a developer.
You have looked at both before dismissing the 'copycat' point, I assume?
Then again, I've got work stacked up literally for 5 years writing client applets.
In fact, I see I think every single statement you make in that post is obviously false.
Disclaimer: Opinions posted on Free Republic are those of the individual posters and do not necessarily represent the opinion of Free Republic or its management. All materials posted herein are protected by copyright law and the exemption for fair use of copyrighted works.