Unter Sprachunabhängigkeit wird in dieser Ausarbeitung die Fähigkeit von Code verstanden, mit Code zu interagieren, der in einer anderen Programmiersprache geschrieben wurde.
Jim Miller, einer der .NET Architekten:
"Ich möchte nur zwei einfache Dinge tun können - und das schon seit über 30 Jahren. Erstens Programme in einer Sprache schreiben, die mir gefällt, dabei aber Bibliotheken von anderen benutzen, die in anderen Sprachen entwickelt wurden. Zweitens Bibliotheken in einer Sprache meiner Wahl schreiben, die dann von anderen in ihren Sprachen genutzt werden."
Abbildung 5: Architektur der an der Sprachunabhängigkeit beteiligten Komponenten
Das Common Type System ist das Typsystem der .NET-Programmiersprachen. Es ist konsequent objektorientiert und enthält alle allgemein anerkannten OOP-Konzepte wie Klassen, Schnittstellen, Vererbung, Polymorphie, virtuelle Methoden usw. Das CTS unterstützt aber auch funktionale und prozedurale Programmiersprachen.
Das Typsystem von .NET gibt keine bestimmte Syntax und keine Schlüsselwörter vor. So kann jede Sprache ihre eigene Syntax nach Belieben festlegen.
Das CTS kann mehr als herkömmliche Typsysteme von z.B. C++ oder Java. Es bietet beispielweise Properties (Eigenschaftenmethoden), Attribute (typisierte erweiterbare Metadaten) oder Delegates (typisierte Funktionszeiger).
Abbildung 6: Common Type System
Das Common Type System ist eine Vereinigung der Typsysteme der .NET-Programmiersprachen. Microsoft hat wärend der Entwicklung intensiv mit Compilerherstellern gearbeitet, um das CTS den Bedürfnissen möglichst vieler Programmiersprachen anzupassen. Es bietet mehr Typen als die meisten Programmiersprachen benötigen. So kennt VB.NET z.B. keine vorzeichenlosen Ganzzahlen.
Abbildung 7: Typklassifikation
Jeder Typ ist in dem Common Type System von Object abgeleitet. Die Typen lassen sich in Referenz- und Werttypen unterteilen. Instanzen von Wertetypen werden auf dem Stack erstellt, während für die Instanz eines Referenztypen nur eine Referenz auf dem Stack vorhanden ist und der Wert selbst auf dem Heap liegt. Es existieren vordefinierte Typen wie Boolean oder Integer, es können aber auch eigene Typen wie Enum oder Struct erstellt werden. Durch Box-Operationen können Referenz- und Werttypen ineinander umgewandelt werden.
Die Common Language Specification ist eine Untermenge des Common Type Systems. Sie definiert einen Satz von Regeln, die jede Sprache einhalten muss, um interoperabel zu sein. Die Regeln gelten aber nur für Typen, auf die von außen zugegriffen werden darf. Intern gelten für eine Sprache keine Einschränkungen.
Einige der Regeln sind z.B.:
CLS-konforme Typen sind z.B.: Byte, Int16/32/64, Single, Double, Boolean, Char, ...
Abbildung 8: Common Language Specification
Die Common Language Specification ist ein Schnitt der Typsysteme der .NET-Programmiersprachen.
Die Common Intermediate Language ist die Zielsprache des .NET Frameworks. Die Compiler erzeugen keinen Machinencode mehr, sondern eine Zwischensprache.
Die CIL ist ein virtueller Befehlssatz und ähnelt dem Befehlssatz eines Prozessors. Ein paar der Befehle lauten z.B.:
Ruft eine Methode auf.
Erzeugt ein neues Objekt.
Lädt ein Element eines Arrays.
Addiert die beiden obersten Werte auf dem Stack und legt das Ergebnis ebenfalls dort ab.
Die Anweisungen sind typunabhängig. Hierzu ein kleines Beispiel zum Vergleich von Java-Bytecode und Common Intermediate Language, das zwei Werte lädt, sie addiert und wieder abspeichert.
Der Beispielcode in Java-Bytecode. Die Werte sind vom Typ Integer.
iload_0
iload_1
iadd
istore_2
Das gleiche Beispiel in Java-Byte nochmal. Diesmal sind die Werte vom Typ Float.
fload_0
fload_1
fadd
fstore_2
Als Vergleich dazu sei hier die Implementation in Common Intermediate Language aufgeführt:
ldloc.0
ldloc.1
add
stloc.2
Die Typbestimmung geschieht in Java-Bytecode durch die Anweisung selbst. Bei der Erzeugung der CIL werden Metadaten mit erzeugt, die die Typbestimmung ermöglichen. Dadurch wird die Erzeugung der CIL einfacher. Die Arbeit der Compiler, der den nativen Code erzeugt wird erschwert, weil der Compiler den Typ mit Hilfe der Metadaten bestimmen muss.
Die Zwischensprache ist streng typisiert, prozessorunabhängig und Stack-orientiert. Sie spiegelt das Common Type System wieder und stellt die Assemblersprache der Common Language Runtime dar.
Eine Zwischensprache kann zwar interpretiert oder kompiliert werden. Laut den Standards ist es aber verboten, die CIL zu interpretieren. Sie wird kompiliert. Dazu gibt es zwei Möglichkeiten:
Der Code wird Stück für Stück kompiliert. Jedes Mal wenn ein Stück Code erreicht wird, das noch nicht kompiliert ist, wird der Compiler gestartet.
Der Code wird ein mal komplett in Maschinencode kompiliert, der dann nicht mehr plattformunabhängig ist.
Sobald ein Quellcode in die CIL übersetzt wurde, ist die Quellsprache nicht feststellbar. Werden im Quellcode die Regeln der Common Language Specification eingehalten, kann der Quellcode in anderen Sprachen verwendet werden. Der Zielcode ist von der Quellsprache unabhängig.
Abbildung 9: "Hello World" in verschiedenen Sprachen
Der Common Intermediate Language Code kann nicht direkt auf einem Prozessor ausgeführt werden. Er wird auf der virtuellen Maschine des .NET Frameworks, der Common Language Runtime, ausgeführt. Sie ist mit der Java Virtual Machine vergleichbar.
Wird ein Programm gestartet, läuft ein kurzes Stück Maschinencode ab, das die CLR in den Prozess lädt und die Kontrolle sofort dorthin abgibt. Die CLR ermittelt den Eintrittspunkt in den Code und startet den Just-In-Time-Compiler. Der Compiler ist extrem schnell. Er kompiliert mehrere Megabyte pro Sekunde und erzeugt optimierten Code für das Zielsystem.
Die CLR lädt aber nicht nur Code und übersetzt ihn, sondern prüft auch seine Korrektheit und Integrität, auch wenn einzelne CIL-Anweisungen untypisiert sind. Die virtuelle Maschine ist auch für die Speicherverwaltung zuständig. Speicher für Referenztypen wird auf dem Heap alloziert und automatisch durch den Garbage Collector wieder frei gegeben, wenn er nicht mehr referenziert ist.
Zusätzlich sorgt die CLR für die Typsicherheit, in dem sie z.B. statisch prüft, ob die auf den Stack geschobenen Parameter der Signatur der aufzurufenden Methode entsprechen.
Für die Sicherheit ist auch die Runtime zuständig. Sie überprpft z.B. die Herkunft des Programms und sorgt dafür, dass die Programme im richtigen Kontext ausgeführt bzw. gar nicht ausgeführt werden und keinen Schaden anstellen.
Eine weitere Aufgabe der CLR ist die Versionsüberprüfung der Bibliotheken. Sie lädt je nach Anforderung die richtige Version. Der Parallelbetrieb mehrerer Versionen ist somit möglich.