14 Einige wichtige .NET-Klassen
In diesem Kapitel lernen Sie einige elementare Klassen des .NET Frameworks kennen, die Sie in Ihren Anwendungen häufig einsetzen werden. Hierzu zählen z. B. die Klasse Object sowie Klassen zum Verarbeiten von Zeichenketten, Datums- und Zeiteinheiten.
14.1 Die Klasse »Object« 

Alle Klassen in der Klassenbibliothek des .NET Frameworks sind Mitglieder einer Klassenhierarchie, die sich über viele Verzweigungen in aufgabenspezifische Bereiche gliedert. Alle Klassen, so tief sie auch im Dickicht dieser Hierarchie stecken mögen, lassen sich aber auf die gemeinsame Basisklasse Object zurückführen. Wenn Sie eine benutzerdefinierte Klasse entwickeln, müssen Sie nicht explizit angeben, dass Ihre Klasse von Object abgeleitet ist – diese Ableitung geschieht implizit. Dass sich alle Klassen von Object ableiten, hat eine ganz wesentliche Konsequenz: Jeder Typ des Systems weist ein Minimum gemeinsamer Verhaltensweisen auf.
Object hat nur einen parameterlosen Konstruktor und insgeamt sieben Methoden. Fünf dieser Methoden sind public und damit öffentlich, die beiden anderen Methoden sind protected und erlauben daher nur den Zugriff aus einer erbenden Klasse heraus. Sehen wir uns zunächst in Tabelle 14.1 alle Methoden in einem Überblick an.
Methoden | Beschreibung |
Equals |
Diese Methode vergleicht zwei Objektreferenzen und liefert einen booleschen Wert zurück, dem entnommen werden kann, ob die beiden Referenzen auf dasselbe Objekt zeigen. |
Finalize |
Dient dazu, Ressourcen der Klasse freizugeben, wenn das Objekt zerstört wird. |
GetHashCode |
Liefert einen objektspezifischen Identifizierer. |
GetType |
Liefert die Referenz auf eine Type-Instanz zurück, die den Typ des aktuellen Objekts beschreibt. |
MemberwiseClone |
Dupliziert die aktuelle Instanz und liefert die Referenz auf das Duplikat zurück. |
ReferenceEquals |
Vergleicht zwei Objektreferenzen und liefert einen booleschen Wert zurück, dem entnommen werden kann, ob die beiden Referenzen auf dasselbe Objekt zeigen. |
ToString |
Liefert den vollqualifizierten Namen einer Klasse. |
14.1.1 Referenzvergleiche mit »Equals« und »ReferenceEquals« 

Die beiden Methoden Equals und ReferenceEquals sind sich per Definition sehr ähnlich. Es werden zwei Objektvariablen miteinander verglichen, um festzustellen, ob beide dasselbe Objekt im Speicher referenzieren:
Demo firstRef = new Demo(); Demo secondRef; secondRef = firstRef; Console.WriteLine(Object.Equals(firstRef, secondRef));
In diesem Codefragment wird die Referenz firstRef der Variablen secondRef zugewiesen. Beide Referenzen zeigen auf dasselbe konkrete Objekt, was der Aufruf der Equals-Methode bestätigt: Es wird true ausgegeben, was als referenzielle Identität der beiden Objektvariablen zu interpretieren ist. In diesem Fall können Sie sogar Equals gegen ReferenceEquals austauschen: Am Ergebnis wird sich nichts ändern.
Equals wird sowohl als Instanz- als auch als Klassenmethode angeboten. Die Instanzmethode ist als virtual gekennzeichnet und kann von jeder Klasse polymorph überschrieben werden. Die statische Equals-Variante ist nicht überschreibbar, ebenso die ähnlich lautende Methode ReferenceEquals. Damit ist auch garantiert, dass das Ergebnis des Aufrufs einer dieser beiden Methoden immer den Vergleich zwischen zwei Objektreferenzen liefert: Es ist true, wenn beide Referenzen auf ein und dasselbe Objekt verweisen, andernfalls lautet das Ergebnis false.
14.1.2 »ToString« und »GetType« 

ToString liefert per Definition eine Zeichenfolge zurück, die den vollqualifizierten Namen der Klasse, also einschließlich der Angabe des Namespace, enthält. Viele Klassen überschreiben diese Methode, die dann einen anderen Rückgabewert hat. Sehen wir uns das an zwei Beispielen an:
string text = "Visual C# 4.0 ist spitze!"; Console.WriteLine(text.ToString()); int value = 4711; Console.WriteLine(value.ToString());
Die Ausgabe lautet:
Visual C# 4.0 ist spitze!
und
4711
Die Typen String und int überschreiben demnach ToString und liefern den Inhalt der Variablen, auf der die Methode aufgerufen worden ist.
Mit GetType können Sie sich den Typ der Klasse besorgen, allerdings müssen Sie dazu die Rückgabe in einen String konvertieren, z. B.:
int value = 10; Console.WriteLine(Convert.ToString(value.GetType()));
Jetzt wird nicht der Inhalt der Variablen value, sondern der Datentyp ausgegeben. Sie müssen an dieser Stelle eine Konvertierung vornehmen, weil der Rückgabewert vom Typ Type ist. Die Klasse Type liefert eine Referenz auf das Type-Objekt eines konkreten Objekts zurück. Dieses versetzt uns in die Lage, den Datentyp einer genaueren Analyse zu unterziehen.
14.1.3 Die Methode »MemberwiseClone« 

Die Methode MemberwiseClone kopiert ein vorhandenes Objekt und liefert die Referenz auf die Kopie. Dabei werden alle Felder des Originals dupliziert: Felder, die auf Wertetypen basieren, werden bitweise kopiert, ebenso die auf Referenztypen basierenden Felder. Das bedeutet, dass die in einem Objekt referenzierten Subobjekte ihrerseits nicht dupliziert werden. Sowohl das Originalobjekt als auch die Kopie greifen auf dieselben Subobjekte zu.
MemberwiseClone ist als protected deklariert und kann nur aus der aktuellen Instanz heraus aufgerufen werden. Sie müssen daher eine öffentliche Methode bereitstellen, auf die externer Code zugreifen kann. Die .NET-Klassenbibliothek bietet dazu mit ICloneable ein Interface an, dessen Sie sich bedienen sollten. Das Interface veröffentlicht nur die Methode Clone. Innerhalb einer zu duplizierenden Klasse wird Clone überschrieben. Clone ruft MemberwiseClone auf das aktuelle Objekt auf und liefert als Rückgabe die Referenz auf das Duplikat:
public object Clone() {
return this.MemberwiseClone();
}
Etwas komplizierter wird es, wenn in einer Klasse Felder definiert sind, die auf Referenztypen basieren, und Sie gleichzeitig auch alle Subobjekte duplizieren möchten. Schauen Sie sich dazu die Implementierung der Klasse CloneableClass an:
public class CloneableClass : ICloneable { public long MyProperty {get; set;} public Demo InternObject = new Demo(); public object Clone() return this.MemberwiseClone(); } ... }
Die Methode Clone wird an einen Aufrufer die Referenz auf eine Kopie zurückliefern. In der Kopie wird das Feld MyProperty denselben Inhalt aufweisen wie das Feld des Originals. Da MemberwiseClone alle Felder bitweise kopiert, referenziert die Eigenschaft InternObject des Duplikats jedoch dasselbe Objekt wie das Original. Benötigen wir auch vom internen Objekt eine Kopie, haben wir zwei Möglichkeiten:
- Wir erzeugen ein neues Objekt vom Typ Demo und weisen diesem alle Eigenschaftswerte der Instanz InternObject explizit zu.
- Wir hoffen, dass der Entwickler der Klasse Demo weitsichtig genug war und die Schnittstelle ICloneable-Methode implementiert hat.
Wir wollen an einem Beispiel die unter Punkt 2 genannte Variante ansehen. Das Objekt dupliziert sich selbst mit MemberwiseClone und liefert die Referenz der Kopie an den Aufrufer zurück. Exemplarisch nehmen wir die folgende Implementierung an:
public class Demo : ICloneable { public int Value {get; set;} public Demo() { Random rand = new Random(); Value = rand.Next(0, 1000); } public object Clone() { return this.MemberwiseClone(); } }
Beim Aufruf des parameterlosen Konstruktors wird nach dem Zufallsprinzip eine Zahl zwischen 0 und kleiner 1000 in der Instanzvariablen Value festgehalten. Sie dient dazu, später den Erfolg des Klonens zu beweisen.
Nun können wir uns noch einmal der Klasse CloneableClass zuwenden und die Methode Clone gemäß unseren Anforderungen überarbeiten.
public class CloneableClass : ICloneable { public long MyProperty {get; set;} public Demo InternObject = new Demo(); public object Clone() { CloneableClass internObj; internObj = (CloneableClass)this.MemberwiseClone(); InternObject = (Demo)this.InternObject.Clone(); return internObj; } }
Zuerst deklarieren wir in der Methode Clone die Variable internObj vom Typ CloneableClass und weisen dieser über MemberwiseClone die Referenz auf das Duplikat zu:
internObj = (CloneableClass)this.MemberwiseClone();
Der Rückgabewert von MemberwiseClone ist vom Typ Object und muss in den Typ CloneableClass konvertiert werden. Die Kopie referenziert noch dasselbe interne Objekt wie das Original. Vom Objekt InternObject besorgen wir uns deshalb eine Kopie und weisen diese dem Feld InternObject der Klone zu.
Damit haben wir unser Ziel erreicht. Was uns jetzt noch fehlt, ist die Bestätigung unserer Überlegungen. Dazu benutzen wir die Methode GetHashCode, da sich jedes Objekt durch einen eigenen Hashcode identifizieren lässt.
// --------------------------------------------------------- // Beispiel: ...\Kapitel 14\MemberwiseClonen // --------------------------------------------------------- class Program { static void Main(string[] args) { CloneableClass obj = new CloneableClass(); CloneableClass dupli = (CloneableClass)obj.Clone(); Console.WriteLine("Hashcode(obj) = {0}", obj.GetHashCode()); Console.WriteLine("Hashcode(dupli) = {0}", dupli.GetHashCode()); Console.WriteLine("Hashcode(obj.InternObject) = {0}", obj.InternObject.GetHashCode()); Console.WriteLine("Hashcode(dupli.InternObject) = {0}", dupli.InternObject.GetHashCode()); Console.WriteLine("Value(obj) = {0}", obj.InternObject.Value); Console.WriteLine("Value(dupli) = {0}", dupli.InternObject.Value); Console.ReadLine(); } }
Der Testcode könnte an der Konsole zur folgenden Anzeige führen:
Hashcode(obj) = 70 Hashcode(dupli) = 72 Hashcode(obj.internClass) = 73 Hashcode(dupli.internClass) = 74 internVar(obj) = 768 internVar(dupli) = 768
Die Hashcodes der Referenzen dupli und obj sind unterschiedlich, ebenso die des enthaltenen Feldes vom Typ Demo. Daraus kann der Schluss gezogen werden, dass das Demo-Feld des Originals ebenfalls geklont worden ist.