1. Herfried K. Wagner’s VB.Any
  2. .NET
  3. Artikel

Der Basistyp von Schnittstellentypen

Einleitung

In einer Diskussion in den öffentlichen Microsoft-Newsgroups wurde die Frage aufgeworfen, warum Visual Basic .NET Methoden der Klasse System.Object nicht für Verweise vom Typ einer Schnittstelle bereitstellt. Während in C# Verweise eines Schnittstellentyps direkten Zugriff auf Mitglieder des Typs Object gewähren, ist dieser in Visual Basic .NET Verweisen vom Typ einer Klasse vorbehalten. Ziel dieses Artikels ist, der Frage nachzugehen, ob Schnittstellen vom Typ Object erben und wie Spezifikationen und Implementierungen der CLR und der Programmiersprachen Visual Basic .NET und C# dies handhaben. Bei diesem Artikel handelt es sich um eine erweiterte Zusammenfassung der Diskussion aus der Newsgroup, die nicht den Anspruch stellt, vollständig zu sein. Demnach sind getätigte Schlüsse nur auf Basis der betrachteten Punkte gültig.

Motivation

Der in der Einleitung dargestellte Unterschied zwischen Visual Basic .NET und C# hinsichtlich des Verhaltens beim Aufruf von Methoden der Klasse Object an Schnittstellenverweisen soll nun anhand eines gegenüberstellenden Beispiels demonstriert werden. Folgendes Listing zeigt äquivalente Codebeispiele in Visual Basic .NET und C#, bestehend aus Definitionen der Schnittstelle IBar, der Klasse FooBar, welche die Schnittstelle IBar implementiert, und einer Variablen iref vom Typ IBar, die auf eine Instanz der Klasse FooBar verweist. Im Anschluß daran wird die Methode ToString der Variablen iref aufgerufen und deren Rückgabewert einer weiteren Variablen zugewiesen. Während der C#-Code kompilierbar ist, führt der Versuch, den Visual Basic .NET-Code zu kompilieren, zum Compilerfehler BC30456 („ToString ist kein Member von IBar“).

Public Interface IBar
    '
End Interface

Public Class FooBar
    Implements IBar

    '
End Class
.
.
.
Dim iref As IBar = New FooBar()
Dim s As String = iref.ToString()

Visual Basic .NET-Beispiel für den Zugriff auf Mitglieder des Typs Object an einem Verweis eines Schnittstellentyps.

public interface IBar
{
    //
}

public class FooBar : IBar
{
    //
}
.
.
.
IBar iref = new FooBar();
string s = iref.ToString();

C#-Version des vorigen Beispiels.

Um den Visual Basic .NET-Code kompilierbar zu machen, muß bar vor Aufruf der Methode ToString in einen Typ umgewandelt werden, der von Object erbt. Dies kann über die Operatoren CObj, DirectCast, CType oder durch Zuweisen an eine Variable des Typs Object oder eines anderen Basistyps von FooBar erfolgen. Dabei ist unerheblich, ob es sich dabei um einen Typ handelt, von dem geerbt werden muß, jedoch existiert nicht immer ein derartiger Typ. Eine Umwandlung in den Typ FooBar funktioniert zwar, stellt aber die Verwendung einer Variablen des Basisschnittstellentyps IBar in Frage. Das folgende Listing demonstriert die beschriebenen Vorgehensweisen am Beispiel aus dem vorhergehenden Listing.

Dim s As String
Dim iref As IBar = New FooBar()

' 1.
s = CObj(iref).ToString()

' 2.
s = DirectCast(iref, Object).ToString()

' 3.
Dim oref As Object = iref
s = oref.ToString()

Möglichkeiten zum Zugriff auf Mitglieder des Typs Object an Verweisen eines Schnittstellentyps.

Alle instanzierbaren Typen erben von Object und Verweise mit Schnittstellentypen verweisen immer auf Typinstanzen oder enthalten einen Nullverweis, also einen Verweis auf Nothing. Daraus folgt, daß jedes Objekt, dessen Methoden über einen Verweis eines Schnittstellentyps aufgerufen werden kann, über die Methoden des Typs Object verfügt. Aus technischer Sicht spricht demnach nichts dagegen, Zugriffe auf Objektmitglieder der Klasse Object über einen Verweis mit Schnittstellentyp ohne eine explizite Typumwandlung zu erlauben. An dieser Stelle fragt sich vielleicht der eine oder andere Leser, warum die Zuweisung von iref an oref im dritten Beispiel nicht zu einem Kompilierungsfehler führt. Der folgende Abschnitt über erweiternde Typumwandlungen gibt Antwort auf diese Frage.

Erweiternde Typumwandlungen in Visual Basic .NET

Der Grund für die Kompilierbarkeit des Beispiels 3 aus dem vorhergehenden Listing liegt darin, daß Visual Basic .NET eine erweiternde implizite Typumwandlung von jedem Typ in den Typ Object unterstützt. Unter einer erweiternden Typumwandlung versteht man eine implizite verlustfreie Umwandlung; der Zieltyp kann dabei jeden Wert des Ausgangstyps annehmen. Im Folgenden wird ein Auszug aus der aus Visual Basic Language Concepts – Widening and Narrowing Conversions entnommenen Tabelle der in Visual Basic .NET unterstützten erweiternden Typumwandlungen wiedergegeben.

Data type Widens to data types
[…] […]
Char Char, String
[…] […]
Any type Object
Any type Any base type from which it is derived
[…] […]

[…]

Widening conversions always succeed and can always be performed implicitly.

Implizite Typumwandlungen können bei Zuweisungen oder Vergleichen stattfinden, nicht jedoch direkt an einem Verweis durch bspw. einen Methodenaufruf. Anders ausgedrückt bedeutet dies, daß eine Variable des Typs Char zwar einer Variablen des Typs String ohne explizite Typumwandlung zugewiesen werden kann, eine Variable des Typs Char jedoch nicht über die Methoden des Typs String verfügt. Da jeder Typ in den Datentyp Object erweitert werden kann, ist keine explizite Typumwandlung beim Zuweisen von iref an oref erforderlich. Ein direkter Zugriff auf Mitglieder des Typs Object ist allerdings nicht möglich, da erweiternde Umwandlungen nicht direkt an Verweisen bei Mitgliedszugriff stattfinden können. Der im nachstehenden Listing angegebene Code ist demnach nicht kompilierbar, obwohl Length eine Methode von String ist und Char zu String erweitert werden kann.


' Nicht kompilierbar; 'Length' ist eine Methode der Klasse 'String'.
Dim c As Char = "a"c
Dim n As Integer = c.Length

' Kompilierbar, da eine erweiternde Typumwandlung zwischen 'Char' und
' 'String' definiert ist.
Dim s As String = c

' Nicht kompilierbar.
Dim i As Integer = 5
If i.TryParse(c) Then...

' Kompilierbar.
Dim d As Double = i
If d.TryParse(c) Then...

Beispiele zu erweiternden Typumwandlungen in Visual Basic .NET.

Ähnlich verhält es sich bei Schnittstellentypen und deren Erweiterung zum Typ Object. Die Erweiterung findet nicht statt, wenn eine Methode einer Variablen aufgerufen wird, deren Typ diese Methode nicht besitzt. Würden erweiternde Typumwandlungen auch direkt an einem Verweis stattfinden, käme es schnell zu Mehrdeutigkeiten, wenn mehrere Typen, in die erweitert werden kann, gleichnamige Methoden besitzen. Zudem wäre es unintuitiv und semantisch inkorrekt, wenn bspw. Mitglieder der Klasse String direkt über eine Variable des Typs Char zugänglich wären. Dieses Verhalten gilt auch für gemeinsame Mitglieder, wie im vorigen Beispiel an der Methode Double.TryParse gezeigt wurde. In den folgenden beiden Listings wird das Verhalten von Visual Basic .NET in Bezug auf erweiternde Typumwandlungen demonstriert. Nachstehendes Beispiel zeigt, unter welchen Bedingungen erweiternde implizite Typumwandlungen stattfinden.

Dim iref As IFoo
Dim t As Type

' Kompilierbar, erweiternde Typumwandlung.
Dim oref As Object = i
t = oref.GetType()

' Nicht kompilierbar, da keine Typumwandlung im Code vorgenommen wird.
t = iref.GetType()

Erweiternde Typumwandlungen und Zugriff auf Mitglieder des Typs Object an Verweisen eines Schnittstellentyps.

Die erweiternde Typumwandlung zwischen abgeleitetem Typ und einem der Basistypen stellt den Trivialfall dar, trifft aber nicht auf die Umwandlung zwischen Schnittstellentypen und dem Typ Object zu. Daraus folgt, daß Object nicht Basistyp von Schnittstellentypen ist. Folgendes Listing zeigt ein weiteres Beispiel, dem zu entnehmen ist, unter welchen Umständen implizite erweiternde Typumwandlungen vorgenommen werden.

Dim c As Char = "a"c
Dim n As Integer

' Kompilierbar, da eine erweiternde Typumwandlung definiert ist.
Dim s As String = c

' Kompilierbar.
n = s.Length

' Nicht kompilierbar, da keine Typumwandlung im Code vorgenommen wird.
n = c.Length

Erweiternde Typumwandlungen am Beispiel der Typen Char und String.

Ist Object ein Basistyp von Schnittstellen?

Zur Klärung der Frage, welche der beiden Programmiersprachen Visual Basic .NET und C# sich in seinem Umgang mit Verweisen von Schnittstellentypen näher an die Vorgaben von .NET hält, sollen in den folgenden Abschnitten Dokumentation und Implementierung von CLR, Visual Basic .NET und C# in dieser Hinsicht näher betrachtet werden. Sofern nicht weiter angegeben, wurden die mit Visual Studio .NET 2003 mitgelieferte Version der Dokumentation und darin enthaltene Compiler und Bibliotheken zur Auswertung benutzt.

Die nachstehende Tabelle gibt einen Überblick über die Ergebnisse dieser Untersuchung. Wenn man alle berücksichtigen Ausschnitte aus Dokumentation und den Implementierungen für eine Einschätzung heranzieht, kann festgestellt werden, daß sich Visual Basic .NET mehrheitlich an die im .NET Framework und der CLR vorgegebene Semantik hält und C# dieser mehrheitlich widerspricht. Innerhalb keiner der drei Gruppen bietet sich kein konsistentes Bild, in dem Dokumentation und Implementierung einander nicht widersprechen. Es ist zu beachten, daß die Anzahl der Kreuzchen in der Tabelle für oder wider der Ansicht, daß Object Basistyp von Schnittstellentypen ist, keine quantitative, allgemein gültige Aussage zulassen, da die Liste keinesfalls den Anspruch der Vollständigkeit erhebt.

Object ist Basistyp von Schnittstellentypen
.NET Framework und CLR Eigenschaft Type.BaseType Implementierung Nein
Dokumentation Ja
Kommentar in der SSCLI Nein
Implementierung der Methode Type.GetMethods Nein
Implementierung der Methode Type.GetMethod Nein
Visual Basic .NET Aufruf an Schnittstellenverweis Nein
Objektbrowser Nein
Sprachspezifikation Kap. 7.8 Nein
Kap. 7.1 Ja
C# Aufruf an Schnittstellenverweis Ja
Objektbrowser Ja
Sprachspezifikation Kap. 1.2.1 Ja
Kap. 7.3.1 Ja

Übersicht über Hinweise auf den Basistyp von Schnittstellentypen in Dokumentation und Implementierung.

.NET Framework und CLR

Dokumentation zur Eigenschaft Type.BaseType

Aus .NET Framework Class Library – Type.BaseType Property:

Interfaces inherit from Object and from zero or more base interfaces; therefore, the base type of an interface is considered to be Object. The base interfaces can be determined with GetInterfaces or FindInterfaces.

Object ist Basistyp von Schnittstellentypen.

Implementierung der Eigenschaft Type.BaseType

Die Dokumentation der Eigenschaft Type.BaseType steht im Widerspruch ihrer Implementierung im .NET Framework. Für Schnittstellen, die keine weiteren Schnittstellen erben, gibt BaseType einen Nullverweis (Nothing) zurück, bei einem Typ, der die Schnittstelle implementiert und direkt von Object erbt, wird Object zurückgegeben. BaseType gibt, wie nicht anders zu erwarten, unabhängig von der aufrufenden Programmiersprache den selben Wert zurück.

Dim t1 As Type = GetType(IBar)
MsgBox(t1.BaseType.ToString())  ' 'NullReferenceException' wird geworfen.

Dim t2 As Type = GetType(FooBar)
MsgBox(t2.BaseType.ToString())  ' 'System.Object'.

Ermitteln des Basistyps eines Schnittstellentyps mittels der Eigenschaft Type.BaseType.

Object ist nicht Basistyp von Schnittstellentypen.

Kommentar in der SSCLI bei der Eigenschaft Type.BaseType

Das Verhalten von Type.BaseType, im Falle des Aufrufs für einen Schnittstellentyp einen Nullverweis zurückzugeben, wird auch in einem Kommentar in der SSCLI-Implementierung vermerkt.

Aus Rotor Source Code – System.Type:

// Returns the base class for a class.  If this is an interface or has
// no base class null is returned.  Object is the only Type that does not
// have a base class.
/// <include file='doc\Type.uex' path='docs/doc[@for="Type.BaseType"]/*' />
public abstract Type BaseType {
    get;
}

Object ist nicht Basistyp von Schnittstellentypen.

Implementierung der Methode Type.GetMethods

Die Methode Type.GetMethods dient dazu, zur Laufzeit Mitglieder eines Typs zu ermitteln. Das von der Methode GetMethods zurückgegebene Array von MethodInfo-Objekten ist leer. Der nach Visual Basic .NET übertragene Code aus dem nächsten Listing würde ebenfalls ein leeres Array zurückliefern, da in beiden Fällen die selbe Methode aufgerufen wird. Die im Beispielcode verwendete Kombination von BindingFlags müßte, sollte der Schnittstellentyp von Object erben, auch dessen Mitglieder zurückgeben. Gleich verhält es sich bei der Methode Type.GetMethod, mit der Informationen zu einer einzelnen Methode bestimmt werden können. In der Dokumentation zu den beiden Methoden wird keine Aussage bezüglich des Verhaltens bei Abruf von Mitgliedern des Typs Object für Schnittstellentypen getroffen.

Type t = typeof(IBar);
MethodInfo[] mi =
    t.GetMethods(
        BindingFlags.Public |
        BindingFlags.NonPublic |
        BindingFlags.Instance |
        BindingFlags.FlattenHierarchy |
        BindingFlags.Static
    )
;

Ermitteln der Methoden eines Schnittstellentyps mittels der Methode Type.GetMethods.

Object ist nicht Basistyp von Schnittstellentypen.

Visual Basic .NET

Zugriff auf Mitglieder der Klasse Object an Verweisen eines Schnittstellentyps

Wie im Abschnitt „Motivation“ gezeigt wurde, unterstützt Visual Basic .NET nicht den Zugriff auf Mitglieder der Klasse Objekt an Verweisen eines Schnittstellentyps. Das nächste Listing zeigt zur Erinnerung das nicht kompilierbare Codebeispiel.

Dim iref As IBar = New FooBar()

' Nicht kompilierbar.
Dim s As String = iref.ToString()

Direkter Zugriff auf Mitglieder des Typs Object an einem Verweis eines Schnittstellentyps in Visual Basic .NET.

Object ist nicht Basistyp von Schnittstellentypen.

Kapitel 7.1 „Value Types and Reference Types“ der Sprachspezifikation

Aus Visual Basic Language Specification – 7.1 Value Types and Reference Types:

Value types and reference types are unified under the type Object, which is the root type of all types.

Object ist Basistyp von Schnittstellentypen.

Kapitel 7.8 „Interfaces“ der Sprachspezifikation

Im Abschnitt Visual Basic Language Specification – 7.8 Interfaces werden zwei Aussagen zu Basistypen von Schnittstellen getroffen:

Aus Abschnitt 7.8.1:

The base interfaces of an interface are the explicit base interfaces and their base interfaces.

Aus Abschnitt 7.8.2:

The members of an interface consist of the members introduced by its member declarations and the members inherited from its base interfaces.

Das bedeutet, daß Schnittstellentypen nicht, auch nicht implizit, vom Typ Object erben.

Object ist nicht Basistyp von Schnittstellentypen.

Der Operator TypeOf...Is

In Visual Basic .NET kann der Operator TypeOf...Is benutzt werden, um zu überprüfen, ob der Laufzeittyp eines Wertes kompatibel zu einem bestimmten Typ ist. Der Rückgabewert des Operators ist True, wenn der Laufzeittyp des Operanden vom Typ erbt oder den Typ implementiert. Die Implementierung von TypeOf...Is entspricht diesem in Abschnitt 11.5.2 „TypeOf...Is Expressions der Sprachspezifikation angegebenen Verhalten. Es gilt zu beachten, daß TypeOf...Is nicht den Typ einer Variablen, sondern den Laufzeittyp des Objekts, auf welches sie verweist, überprüft. Enthält die Variable einen Nullverweis, wird immer False zurückgegeben.

Dim iref As IBar = Nothing
Console.WriteLine(TypeOf iref Is Object)    ' 'False'.

iref = New FooBar()
Console.WriteLine(TypeOf iref Is Object)    ' 'True'.

Ermitteln des Laufzeittyps eines Objekts mittels des Operators TypeOf...Is.

→ Keine Aussage, da der Laufzeittyp einer Klasseninstanz geprüft wird.

Objektbrowser in Visual Basic .NET 2003

Der Objektbrowser von Visual Basic .NET 2003 führt Object nicht als Basistyp von Schnittstellen auf, unabhängig davon, ob die Anzeige erweiterter Mitglieder aktiviert ist oder nicht. Die folgende Abbildung zeigt das Aussehen des Objektbrowsers mit Auswahl einer Schnittstelle. Der Objektbrowser entspricht dem in Visual Basic .NET mehrheitlich vorzufindenden Verhalten.

[Illustration] Ansicht eines Schnittstellentyps im Objektbrowser von Visual Basic .NET

Ansicht eines Schnittstellentyps im Objektbrowser von Visual Basic .NET 2003.

Object ist nicht Basistyp von Schnittstellentypen.

C#

Zugriff auf Mitglieder des Typs Object an Verweisen eines Schnittstellentyps

C# erlaubt den Zugriff auf Mitglieder des Typs Object an Verweisen eines Schnittstellentyps. Der im nachstehenden Listing gezeigte Code illustriert dieses Verhalten, das im Widerspruch zu den von Reflection zurückgegebenen Informationen steht. Der Grund für die Zulässigkeit des untenstehenden Codes dürfte darin zu finden sein, daß zur Laufzeit ein Verweis eines Schnittstellentyps entweder einen Nullverweis darstellt oder aber auf eine Instanz eines die Schnittstelle implementierenden, instanzierbaren Typs verweist, sodaß kein Unterschied zu Verweisen eines instanzierbaren Typs vorliegt.

IBar iref = new FooBar();

// Kompilierbar.
string s = iref.ToString();

Direkter Zugriff auf Mitglieder des Typs Object an einem Verweis eines Schnittstellentyps in Visual C# .NET.

Object ist Basistyp von Schnittstellentypen.

Kapitel 1.2.1 „Predefined types“ der Sprachspezifikation

Aus C# Language Specification – 1.2.1 Predefined types:

The type object is the ultimate base type of all other types.

Object ist Basistyp von Schnittstellentypen.

Kapitel 7.3.1 „Base types“ der Sprachspezifikation

Aus C# Language Specification – 7.3.1 Base types:

For purposes of member lookup, a type T is considered to have the following base types:

[…]

  • If T is an interface-type, the base types of T are the base interfaces of T and the class type object.

Object ist Basistyp von Schnittstellentypen.

Der Operator is

Der Operator is in C# entspricht dem Operator TypeOf...Is von Visual Basic .NET, weshalb auf eine genauere Beschreibung verzichtet wird. Dennoch ist das Verhalten des C#-Compilers in Bezug auf den is-Operator beachtenswert. Betrachten wir dazu den im folgenden Listing wiedergegebenen Code. Der Versuch, den Code zu kompilieren, führt zur Compilerwarnung CS0183 („Der Ausdruck ist immer vom bereitgestellten (object) Typ“). Das Auftreten dieser Warnung steht in Einklang zur Sprachspezifikation. Allerdings evaluiert der Ausdruck iref is object bei Ausführung des Codestücks zu False, wenn iref einen Nullverweis enthält. Der Compiler überprüft anscheinend den Typ der Variablen zur Kompilierungszeit, obwohl der Operator is zur Überprüfung des Laufzeittyps vorgesehen ist. Im Compiler für C# 2.0 ist das Problem behoben, es wird keine Warnung mehr angezeigt.

IBar iref = null;

// Warnung CS0182.
Console.WriteLine(iref is object);  // 'False'(!).

iref = new FooBar();
Console.WriteLine(iref is object);  // 'True'.

Ermitteln des Laufzeittyps eines Objekts mittels des Operators is.

Aus C# Programmer’s Reference – is:

The is operator is used to check whether the run-time type of an object is compatible with a given type.

→ Keine Aussage, da der Laufzeittyp einer Klasseninstanz geprüft wird.

Objektbrowser in Visual C# 2003

Der Objektbrowser von C# führt Object als Basistyp von Schnittstellentypen an. In der folgenden Abbildung ist das Aussehen des Objektbrowsers von C# für einen Schnittstellentyp und dessen Basisklassen und Schnittstellen zu sehen. Die Darstellung entspricht der Sicht, die in IntelliSense geboten und vom C#-Compiler zugelassen wird.

[Illustration] Ansicht eines Schnittstellentyps im Objektbrowser von Visual C# .NET

Ansicht eines Schnittstellentyps im Objektbrowser von Visual C# .NET.

Object ist Basistyp von Schnittstellentypen.

Herstellen eines konsistenten Verhaltens

Im Folgenden sollen drei Möglichkeiten der Vereinheitlichung des Verhaltens der Programmiersprachen Visual Basic .NET und C# sowie des .NET Frameworks in Bezug auf Schnittstellen und deren Basistypen zur Herstellung konsistenten Verhaltens vorgestellt werden.

Schlußwort

Der Typ Object ist nicht als Basistyp von Schnittstellentypen anzusehen, wohl aber als Basistyp von Typen, die Schnittstellen implementieren. Dies steht nicht im Widerspruch dazu, daß es sich bei Schnittstellen um Typen handelt. Die Inkonsistenzen in der Bedeutung von Schnittstellen und die Unklarheit über ihre Basistypen sind entweder auf Mängel in der Planungsphase der CLR und des .NET Frameworks zurückzuführen oder es handelt sich um Spuren der geschichtlichen Gewordenheit der Technologien.

Ein allgemeines Bereinigen der Inkonsistenzen ist wohl aufgrund der Unmenge an Code, der bereits für .NET entwickelt wurde, nicht möglich. Die betrachteten Dokumentationsausschnitte und Implementierungen lassen den Schluß zu, daß es zumindest möglich ist, innerhalb der drei Bereiche CLR und .NET Framework, Visual Basic .NET und C# einen widerspruchsfreien Zustand herzustellen. Dokumentation und Objektbrowser können angepaßt werden, ohne bestehenden Code in Mitleidenschaft zu ziehen. Inkonsistenzen zwischen den Bereichen sind zwar ärgerlich, aber von geringerem Hindernis. Es bleibt zu hoffen, daß in zukünftigen Versionen des .NET Frameworks und der beiden Programmiersprachen versucht wird, bestehende Widersprüche auszuräumen.