Zum Inhalt springen

.NET – Types and Base Types (Inheritance)

Juni 6, 2011

 

Abgrenzung: es geht in diesem Artikel um “concrete types”, nicht “abstract types” (Interfaces sind implizit “abstract”) !!!

 

Das .NET Typsystem (CTS) ist so implementiert, dass alle anderen Typen von einen Stammtypen Namens System.Object erben müssen. Das .NET Typsystem unterstützt, im Zusammenhang mit “concrete types”, im Gegensatz zu C++, lediglich eine Einfach-Vererbung. Das heißt jeder Typ kann nur von einem (Basis-)Typen erben. Wenn kein Typ angegeben ist, erbt es, sofern es ein Referenz-Typ ist, implizit von System.Object. Wenn es aber ein Value-Typ ist, erbt es implizit von System.ValueType, das wiederum von System.Object erbt. Genau genommen unterstützt es diese auch nur für Referenz-Typen, da Value-Typen implizit von System.ValueType erben und selber nicht weiter-vererbt werden können. Referenz-Typen hingegen können von anderen Typen erben und auch, falls nicht explizit unterbunden (sealed), weiter-vererbt werden. Zudem kann ein Typ als “abstract” deklariert werden, so dass es nicht instanziiert werden kann bzw. nur als Basistyp eingesetzt werden darf aber dazu mehr in einem der folgenden Blogeinträge… 

Durch die Vererbung enthält der Sub-Typ alle Member des Basis-Typen, außer der privaten (auch wenn Base und Sub-Typ in unterschiedlichen Assemblies liegen). Der Sub-Type kann auf alle nicht “private” Member des Basis-Typen zugegriffen. Falls im “base type” und “sub type” Felder deklariert sind, die den gleichen Namen haben, enthält der Sub-Type beide Felder. Um diese auseinander halten zu können, bietet C# die keywords “this” und “base”. Falls es sich aber bei den Feldern um statische Felder handelt kann der Typ-Name vorangestellt werden um sie auseinander zu halten.

Aber was ist wenn der Sub-Type in den Basistypen gecastet wird? Auf welches Feld erfolgt dann der Zugriff? In diesem Fall entscheidet der “Laufzeit-Typ”. Das heißt, wenn Base- und Sub-Typ z. B. ein redundantes Feld Namens “age” führen und eine Instanz des Sub-Typen in den Basistypen gecastet wird, und anschließend auf “age” zugegriffen wird (vorausgesetzt es ist nicht “private”) wird tatsächlich auf das “age”-Field im Basistypen zugegriffen.

Hinweis: der C# Compiler bringt in diesem Fall eine Warnmeldung, welche aussagt, dass das “age” Field des Basistypen durch das “age” Field des Sub-Typen verdeckt wird. Diese Warnung kann aber unterbunden werden, wenn vor der Field-Deklaration das keyword “new” angegeben wird. In VB.NET ist das besser gelöst, denn da heißt das keyword “Shadows”. In C# ist das insofern schlechter gelöst, da dem “new” keyword eine neue Bedeutung zukommt!

 

Member-Collision (duplicate method names or signatures equal)

Da im Rahmen der Methoden-Überladungen doppelte Methoden-Namen vorkommen können, unterstützt das Typsystem zwei Arten für die Kollisionserkennungen bzw. –Lösung: “hide-by-name” and “hide-by-signature”. Dazu muss jede Methode angeben welches Kollisionserkennungsverfahren für ihn gelten soll – dies wird Anhand des “hidebysig”-Metadaten-Attributs gesteuert. Anders als in C++, wo der Sprach-Compiler standardmäßig vom “hide-by-name” ausgeht, verwendet C# standardmäßig die “hide-by-signature” Variante, wo eine Kollision nur dann gegeben ist, wenn die Signature einer Methode (aus dem Sub-Typen) mit der Signatur einer Methode aus dem Basistypen übereinstimmt. In diesem Fall wird die Basis-Methode von der Sub-Methode verdeckt, alle anderen, nicht privaten Basistype-Member werden nach wie vor Bestandteil des Sub-Typen. Der C# Compiler kann zusätzlich für Methoden auch  die “hide-by-name”-Kollisionsvariante definieren indem vor der Methode das keyword “new” gesetzt wird. Das Ganze ist in VB.NET anders implementiert, da dort die Angabe ob “hide-by-name” oder “hide-by-signature” explizit, mittels der keywords “Shadows” (für “hide-by-name”) und “Overloads” (für “hide-by-signature”), anzugeben ist. Bei der C#-Variante ist außerdem schlecht, dass dem “new” keyword eine neue Bedeutung zukommt. Diese ist zwar kontextbasiert aber dennoch schlecht gelöst…

Hinweis: es sollte nicht vergessen werden, dass bei überladenen Methoden erst zur Laufzeit entschieden wird, welche Überladung auszuführen ist.

 

Base Types and Constructors

Ein sehr wichtiger Punkt im Rahmen der Vererbung kommt den Typ-Konstruktoren zu. Dabei werden, immer wenn ein Typ instanziiert wird, erst die Field-Member des zu instanziierenden Typen erzeugt und anschließend der Konstruktor des Typen aufgerufen. Falls der Typ einen Basistypen definiert, wird erst der Konstruktor des Basistypen aufgerufen, bevor die Konstruktor-Anweisungen des Sub-Typen ausgeführt werden . Im Zuge dessen werden natürlich erst wieder die Field-Member initialisiert und anschließend der Konstruktor des Typen aufgerufen. Falls dieser ebenfalls eine Basis-Klasse hat geht die Aufruf-Kaskadierung der Konstruktoren hoch bis zum Stammtypen (System.Object). Erst wenn der oberste Konstruktor erreicht und ausgeführt ist, werden die “unteren” Konstruktoren, entsprechend ihrer Abwärtshierarchie, aufgerufen und ausgeführt.

Bei Standard-Konstruktoren, die vom Sprach-Compiler eingefügt werden, braucht kein extra Code geschrieben werden. Der Sprach-Compiler deklariert die Standard-Konstruktoren so, dass diese der oben beschriebenen Aufruf- und Abarbeitungsreihenfolge folgen. Doch wenn explizite Konstruktoren angegeben werden, muss der Aufruf der Basistype-Konstruktoren selbst angegeben werden. Andernfalls kann es zu unerwünschten Seiteneffekten kommen, wenn z.B. “virtuelle Methoden” aus Basistype-Konstruktoren aufgerufen werden, die wiederum auf ihre Field-Member zugreifen…

ctor initializations

Abb. 1: Constructor-call sequence for Inheritance

 
Quelle: Essentials .NET Volume 1

From → .NET

Kommentar verfassen

Hinterlasse einen Kommentar