Advanced   Java   Services Generics in Collections Back Next Up Home


Warum Generics

Im folgenden das klassische Problem, das zur Einführung von Generics geführt hat. Wir legen eine ArrayList an und nehmen Objekte auf. In Zukunft werden wir das eine ungesicherte ArrayList nennen. Natürlich können wir auch eine andere Containerklasse aus System.Collections wählen. Bis auf die Klasse BitArray deren Elemente alle vom Typ bool sind können alle anderen Containerklassen beliebige C#-Objekte aufnehmen.

ArrayList list = new ArrayList(); //1
list.Add("Hello World");          //2
list.Add(17);                     //3
String hi = (String)list[0];      //4
String low = (String)list[1];     //5  RuntimeException: System.InvalidCastException

Wenn wir annehmen, daß die ArrayList zum Aufnehmen von Strings eingeführt wurde, so ist in Zeile 3 ein Fehler passiert. Beim Zugriff auf die enthaltenen Elemente mit dem Indexer hat diese Methode den Returntyp Objekt, da man beliebige Objekte aufnehmen kann. Nach unserer Information enthält die Liste aber nur Strings, deswegen casten wir alles von Object auf String. Der Compiler kann den Fehler in Zeile 5 nicht feststellen und es kommt zur Laufzeit zu einer InvalidCastException. Die in der Version 2.0 eingeführten Generics ermöglichen es dem Compiler, diesen Fehler zu entdecken und somit wird aus einer RuntimeException ein Compilerfehler.

Vorteil: Generics verwandeln RuntimeExceptions zu Compilerfehlern


Einfache Beispiele

Die Containerklassen aus System.Collections sind nichtgenerisch. Die generischen Containerklassen liegen in System.Collections bzw. in SySystem.Collections.ObjectModel. Die entsprechende Klasse zu ArrayList heißt List<T>. In den spitzen Klammern wird der Typ der Elemente angegeben, die die Containerklasse aufnehmen soll.

List<String> stringList = new List<String>();       // (1)
List<Person> personList = new List<Person>();       // (2)
List<int> intList = new List<int>();                // (3)
List<ValueType> valueList = new List<ValueType>(); // (4)

Durch die Typangabe in den spitzen Klammern (1) erhält die List den generischen Typ String und ist ausschließlich für Strings zuständig. Beim Aufnehmen der Objekte kann der Compiler nun den Typ prüfen. Beim Zugriff über den Indexer ist kein Cast mehr notwendig, der Compiler fügt ihn für uns ein.

List<String> stringList = new List<String>();       // (1)
stringList.Add("Generics sind eine gute Sache");
String s = stringList[0];

Natürlich kann man generische Klassen für jeden beliebigen Typ erzeugen.

List<String> stringList = new List<String>();       // (1)
List<Person> personList = new List<Person>();       // (2)
List<int> intList = new List<int>();                // (3)

Ähnlich wie bei Arrays kann man auch Objekte eines Unterklassentyps aufnehmen (Typverwandtschaft durch Vererbung). So kann man etwa System.valueType oder System.Array als Basistyp wählen und erhält so ein etwas interessanteres Beispiel.

Wenn man System.ValueType wählt, so kann man in diese Liste alle Werttypen aufnehmen, auch selbstentworfene Werttypen, also structs.

List<ValueType> valueList = new List<ValueType>(); // (1)
valueList.Add(17);
valueList.Add(3.14);
valueList.Add(new MyStruct());
ValueType vt1 = valueList[0];
ValueType vt2 = valueList[1];
ValueType vt3 = valueList[2];
Console.WriteLine(vt1.GetType().Name);   // Int32
Console.WriteLine(vt1);  // 17
Console.WriteLine(vt2);  // 3,14
Console.WriteLine(vt3);  // Generics1.MyStruct

Wählt man System.Array, so kann man in diese Liste beliebige Arrays beliebiger Typen aufnehmen.

List<Array> arrayList = new List<Array>(); // (4)
arrayList.Add(new int[] { 1, 2, 3 });
arrayList.Add(new string[] { "eins", "zweins", "dreins" });
Array arr1 = arrayList[0];
Array arr2 = arrayList[1];
Console.WriteLine(arr1);  // System.Int32[]
Console.WriteLine(arr2); //System.String[]
foreach(var elem in arr1)
   Console.Write(elem + " ");
Console.WriteLine();
foreach (var elem in arr2)
   Console.Write(elem + " ");
Console.WriteLine();

Dieses Beispiel ist insofern interessant, weil man hier notwendig das Schlüsselwort var braucht. Man kennt ja den genauen Type des Arrays nicht. Mit var überträgt man diese Aufgabe an den Compiler. var steht immer für einen Typ, den der Compiler aus dem Kontext ermitteln kann.



Werttypen und Autoboxing bzw. Unboxing

In C# können auch Werttypen als generische Typen eingesetzt werden. Trotzdem gibt es aber ein Boxing, da die Containerklassen nur Objekte aufnehmen können. Dieses Boxing bzw. Unboxing findet automatisch statt, der Entwickler braucht sich nicht darum kümmern.


Keine Typverwandtschaft

Durch die Spezialisierung der Collectionklassen mit Hilfe von generischen Typen entstehen keine neuen Datentypen. Ist etwa die Klasse Child1 eine Ableitung der Klasse Parent, so ist List<Parent> nicht typverwandt mit List<Child1>. Um das einzusehen führen wir noch eine Klasse Child2, die ebenfalls von Parent erbt.

Falsch:

List<Child1> child1List = new List<Child>();
List<Parent> parentList = child1List; // Error

Der Compiler verweigert sowohl einen indirekten Cast Cannot implicitly convert List<Child> to List<Parent> wie auch ein direktes Casten Cannot convert List<Child> to List<Parent>.


Generische Collections in Methodensignaturen

Da es in C# kein Type Erasure wie in Java gibt, können ContainerObjekte, die sich nur im generischen Typ unterscheiden vom Compiler erkannt werden, wie das folgende Beispiel zeigt.

So sind beispielsweise die folgenden beiden Signaturen durchaus zulässig.

static void method(List<ValueType> valueList)
{
   Console.WriteLine(valueList.GetType().FullName);
}

static void method(List<Array> arrayList)
{
   Console.WriteLine(arrayList.GetType().FullName);
}

Folgende Methodenaufrufe sind also möglich

static void Main(string[] args)
{
   List<ValueType> valueList = new List<ValueType>();
   List<Array> arrayList = new List<Array>();
   method(valueList);
   method(arrayList);
}

Mit ein wenig Reflection sieht man, daß der Compiler die Signaturen gut unterscheiden kann. Hier die Konsolausgabe

System.Collections.Generic.List`1[[System.ValueType, mscorlib, Version=4.0.0.0,Culture=neutral, PublicKeyToken=b77a5c561934e089]]
System.Collections.Generic.List`1[[System.Array, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Valid XHTML 1.0 Strict top Back Next Up Home