Freigeben über


Dr. GUI .NET 1.1 #4

 

27. Juni 2003

Zusammenfassung: Dr. GUI beschreibt System.Object, die Basisklasse aller anderen .NET Framework-Typen, und zeigt, wie die Methoden in jedem .NET Framework-Typ verwendet werden. Der gute Arzt auch. (nn gedruckte Seiten)

Dr. GUI .NET-Startseite
Quellcode für diesen Artikel

Weitere Informationen finden Sie im Code Ausführen der DynamicCallASPX-Beispielanwendung.

Inhalte

Wo wir waren; Where we're Going System.Object: A very good place to Start The Documented Members Runtime Type Information: Type and GetType Getting Rid of Objects: A bit more on Garbage Collection, Finalize, and Dispose Give it a shot! Was wir getan haben; Was kommt als nächstes

Sprechen Sie aus!

Wenn Sie etwas haben, das Sie sagen möchten, teilen Sie uns und der Welt mit, was Sie über diesen Artikel auf dem Dr. GUI .NET-Nachrichtenboard denken, oder lesen Sie seine neuesten Gedanken in seinem Blog.

Wo wir waren; Wohin wir gehen

Beim letzten Mal haben wir ein ziemlich vollständiges Beispiel für die Vererbung mit abstrakten (MustInherit)-Basisklassen und -schnittstellen in einer einfachen Zeichnungsanwendung gesehen – und wir haben es sowohl mit Microsoft Windows® Forms als auch mit ASP.NET Web Forms getan. Die ASP.NET-Anwendung zeichnete benutzerdefinierte Bitmaps wie ihre Zeichnung. Sehen Sie sich diese coole Anwendung hier an.

Dieses Mal werden wir die Mutter und den Vater aller .NET-Klassen diskutieren: das ehrwürdige System.Object. Außerdem werden Speicherzuordnung und Garbage Collection (GC) erläutert.

System.Object: Ein sehr guter Ausgangspunkt

Im .NET Framework werden alle Objekte direkt oder indirekt von einer gemeinsamen Basisklasse abgeleitet: System.Object. Denken Sie daran, dass die Vererbung/Ableitung uns sagt, dass die abgeleitete Klasse IS-A spezialisierte Version der Basisklasse ist. Das bedeutet, dass jedes Objekt im .NET Framework IS-A(n)Object und daher die gesamte Funktionalität von System.Object implementiert. Mit anderen Worten, die Methoden von System.Object sind in jedem .NET Framework-Objekt verfügbar.

In Anbetracht dessen können Sie sehen, dass es praktisch ist, genau zu wissen, was diese Methoden sind und wie sie verwendet und ggf. überschrieben werden sollen.

Was ist in System.Object?

Da es praktisch ist, zu wissen, was alle Objekte tun können, lassen Sie uns herausfinden, was sich in System.Object befindet. Wie bei allen Dingen in der Programmierung gibt es zwei Versionen dessen, was darin enthalten ist: die Version gemäß der Dokumentation und was wirklich vorhanden ist. Wir sehen uns beides an und zeigen dann, dass Sie aus der Lektüre der Dokumentation ein vollständiges Bild für alle praktischen Zwecke erhalten.

Gemäß der Dokumentation

Gemäß der Dokumentation für System.Object gibt es einen öffentlichen Konstruktor (der keine Parameter akzeptiert und nichts tut) und sechs instance Member: die öffentlichen Member, Equals, GetHashCode, GetType, ToString und die geschützten Member Finalize und MemberwiseClone. Es gibt auch zwei öffentliche statische/freigegebene Methoden: ReferenceEquals und eine Überladung von Equals. Wir besprechen später ausführlich, was die einzelnen dieser Aufgaben sind.

Gemäß ILDASM

Wir haben in Dr. GUI.NET 1.1 #0 und Dr. GUI.NET 1.1 #1 gesehen, dass Sie ILDASM verwenden können, um die Metadaten und die IL Ihrer Programme zu untersuchen. Sie können ILDASM auch verwenden, um die Metadaten und die IL der Systemdateien zu untersuchen. Aus den Metadaten für einen beliebigen Typ (doppelklicken Sie auf die .class-Zeile, die ein rotes Dreieck aufweist), wissen wir, dass System.Object in mscorlib.dllgefunden wird.

Oh wo, oh wo sind meine .NET-Systemdateien?

Sie müssen wissen, wo sich die .NET-Systemdateien befinden, um sie zu finden: Sie befinden sich alle in der Verzeichnisstruktur "Microsoft.NET\Framework" unter Ihrem Windows-Verzeichnis. Jede Version der .NET-Runtime befindet sich in einem separaten Verzeichnis mit dem Namen der Versionsnummer. Auf dem Computer von Dr. GUI ist der Pfad C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorlib.dll.

Beachten Sie, dass die Funktion "Nach Dateien suchen" in Windows 2000 (aber nicht Windows XP) aus irgendeinem Grund bestimmte Systemdateien und Verzeichnisse nicht findet, obwohl sie nicht ausgeblendet sind. Das Programm gibt Ihnen keine Warnung, dass es dies für Sie tut, und die Hilfedatei kann diese Tatsache nicht Erwähnung. Dr. GUI konnte (neben dem Upgrade auf Windows XP) auch keine Möglichkeit finden, dieses unangenehme Verhalten zu beenden.

Basierend auf dem, was wir gerade gelernt haben, können wir die Datei finden (wie Dr. GUI erinnert, funktioniert die Funktion "Nach Dateien suchen" unter Windows 2000, wenn Sie im Verzeichnis "Microsoft.NET" unter Ihrem Windows-Verzeichnis starten). Starten Sie ILDASM dann, und verwenden Sie den Befehl File.Open , um die dateimscorlib.dll zu öffnen.

Wow: Metadaten im System!

Wenn Sie den Systemnamespace öffnen, wird eine Struktur mit den meisten Typen im System aufgeführt. Das Symbol vor jedem Namen gibt an, welche Art von Typ es gibt: Die blauen, nach unten zeigenden pfeilähnlichen Dingies, die jeweils einen roten Streifen aufweisen, sind Namespaces (auch in den Metadaten-Viewern sind Typen in Namespaces enthalten!); die blauen Felder mit den Linien, die rechts herauskommen, sind Verweistypen; die hellbraunen Felder mit den Linien, die rechts herauskommen, sind Werttypen (weitere Informationen zu Werttypen später); die Werttypen mit dem Buchstaben "E" sind aufgezählte Typen; und die Verweistypen mit dem Buchstaben "I" sind Schnittstellen. Die meisten (wenn nicht alle) der Kerntypen .NET Framework sind hier. Sie können deren Metadaten und IL genauso durchsuchen wie Ihre eigenen Typen. (Beachten Sie, dass die IL für einige Methoden ausgeblendet ist, manchmal, weil diese Methode nativen Code aufruft.)

Nicht dokumentierte Mitglieder?

Wenn Sie System.Object öffnen, sehen Sie viel mehr Methoden als oben beschrieben. Das kleine violette Feld zeigt eine Methode an. Sie werden feststellen, dass private Mitglieder nicht von anderen unterschieden werden. Sie müssen auf jedes Element doppelklicken, um zu sehen, ob es privat ist oder nicht.

Wenn Sie dies tun, werden Sie feststellen, dass alle nicht dokumentierten Mitglieder privat sind. Private Mitglieder sind in der Regel nicht in der .NET Framework dokumentiert – schließlich können Sie sie nie aufrufen (zumindest nicht direkt), siehe unten). Tatsächlich änderten mehrere der nicht dokumentierten Mitglieder ihre Namen zwischen Beta1 und Release. Man kann die Namen von nicht dokumentierten Membern problemlos ändern, da sie außer dem Code in dieser bestimmten Klasse nicht verwendet werden sollen.

Die dokumentierten Member sind entweder öffentlich – von jedem Ort aus zugänglich – oder "Family", was der .NET-Name für das ist, was C# "protected" nennt– und nur von derselben oder abgeleiteten Klassen aus zugänglich.

Frankly, Dr. GUI hat keine Ahnung, was die meisten dieser privaten Methoden tun. Das muss er auch nicht. Er stellt sich das FieldSetter/FieldGetter-Paar und GetFieldInfo für die Serialisierung vor, hat aber keine gute Vorstellung von InternalGetType oder FastGetExistingType, obwohl er bemerkt, dass beide von GetType aufgerufen werden.

Der Punkt ist nicht das, was diese Methoden tun, da Sie sie normalerweise nicht aufrufen. (Wie sich herausstellt, können Sie auch private Mitglieder mithilfe von Reflektion aufrufen, vorausgesetzt, Sie verfügen über die richtigen Sicherheitsberechtigungen – das Markieren eines Privaten ist KEIN Sicherheitsmechanismus!) Der Punkt ist, dass Die Metadaten es Ihnen ermöglichen, mehr von dem zu sehen, was sich wirklich in der Klasse befindet, sogar die privaten Member, die nicht dokumentiert sind (da es nicht erforderlich ist, sie zu dokumentieren).

Außerdem ist es eine Anweisung, die dokumentierte Funktionalität der einzelnen Methoden mit der IL zu vergleichen. Für instance wird in der Dokumentation angegeben, dass der Konstruktor das Objekt initialisiert. Object enthält jedoch keine Felder, sodass es nichts zu initialisieren gibt. Wenn Sie die IL für den Konstruktor (mit dem Namen ".ctor" in ILDASM) betrachten, wird folgendes überprüft: Es besteht nur aus einer RET-Anweisung (return to caller). Mit dem gleichen Token macht Finalize in der Klasse Object nichts.

Sie können sich eine Vorstellung davon verschaffen, was einige der anderen Methoden tun, indem Sie sich die IL ansehen und mit der Dokumentation vergleichen.

Die dokumentierten Elemente

Wie wir gerade beschrieben haben, gibt es neun dokumentierte Member: den Konstruktor und die acht Methoden GetType, ToString, Finalize (Destruktor in C#), Equals (mit statischen/freigegebenen und instance-Überladungen), einen statischen/freigegebenenVerweisEquals, MemberWiseClone und GetHashCode. Alle instance Methoden mit Ausnahme von GetType und MemberwiseClone sind virtual/Overridable und können von jedem Typ überschrieben werden, der von Object geerbt wird (wie alle Typen). GetType ist nicht virtuell/überschreibbar , da sich die Implementierung für keinen Typ ändert. MemberwiseClone ist ebenfalls nicht virtuell/überschreibbar , da es sich um eine Hilfsfunktion handelt, die sich auch für keinen Typ ändert. Klassen, die das Klonen unterstützen, sollten die ICloneable-Schnittstelle und ihre Clone-Methode implementieren. (Ihre Implementierung von Clone könnte jedoch Object.MemberwiseClone als Teil oder als teil- oder gesamter Vorgang aufrufen.)

Konstruktor

Wenn Sie keine Konstruktoren definieren, geben die Compiler allen Typen, einschließlich Object, einen Konstruktor, der keine Parameter akzeptiert. (Wenn Sie Konstruktoren definieren, wird dieser Standardkonstruktor nicht mehr verwendet, Sie können jedoch einen eigenen definieren.) In C# ist der Konstruktor eine Methode, die denselben Namen wie die -Klasse hat. Für die Klasse Foo ist der Konstruktor also die Methode, die auch Foo genannt wird. In Microsoft Visual Basic® .NET heißt der Konstruktor immer Neu.

Ihre Konstruktoren sollten immer genau einen Konstruktor in Ihrem Basistyp aufrufen. Die Compiler generieren automatisch einen Aufruf des Konstruktors des Basistyps, der keine Parameter für Sie akzeptiert, aber wenn Sie Parameter an den Konstruktor Ihres Basistyps übergeben möchten, müssen Sie daran denken, den richtigen Konstruktor am Anfang des Konstruktors explizit aufzurufen. (Die Syntax hierfür variiert von Sprache zu Sprache.)

Der Konstruktor des Basistyps ruft einen Konstruktor des unmittelbaren Basistyps usw. auf, und beim Erstellen einer Kette von Konstruktoren wird der gesamte Weg zurück zu System.Object aufgerufen. Der Aufruf des Konstruktors des Basistyps erfolgt , bevor der Textkörper des Konstruktors der aktuellen Klasse ausgeführt wird. Dadurch wird sichergestellt, dass alle Basisklassen erstellt werden, bevor der Konstruktor ausgeführt wird.

Wenn Sie sich den IL für den Konstruktor von Object ansehen, sehen Sie, dass sie nichts tut. Es ruft nicht einmal einen anderen Konstruktor auf. Das liegt daran , dass Object (und Object allein) keine Basisklasse besitzt. Es gibt nichts anderes zu tun, da Object auch keine Felder enthält, die initialisiert werden müssen.

Finalize/Destructor

Wenn Sie ein Objekt in seinem Konstruktor initialisieren können, sollten Sie es auch sauber können. Und in der Tat können Sie in Visual Basic .NET die Finalize-Methode in Ihrer Klasse überschreiben. In C# wird die analoge Methode als Destruktor bezeichnet. Sie wird wie die Klasse mit einem führenden "~" (ähnlich der C++-Syntax) benannt. Das Verhalten von C#-Destruktoren unterscheidet sich jedoch etwas von den analogen Methoden in Visual Basic .NET und C++. (Wir werden dies in Kürze besprechen.)

Im Allgemeinen sollten Sie keine Finalize-Methode/einen Finalize-Destruktor schreiben, es sei denn, Ihr Objekt steuert eine andere Ressource (z. B. Dateihandles, Fensterhandles, Grafikobjekte oder Datenbankverbindungen) als den verwalteten Arbeitsspeicher. Wenn Ihr Objekt lediglich Verweise auf andere Objekte aufweist, kümmert sich die Garbage Collection darum, den gesamten Arbeitsspeicher und andere Objekte ordnungsgemäß freizugeben. Wenn eines dieser Objekte einen Finalizer benötigt, sollte es diese bereitstellen. Wenn Sie eine Finalize-Methode in Visual Basic .NET schreiben, denken Sie daran, das Finalize-Element Ihrer Basisklasse am Ende der Finalize-Klasse aufzurufen (wenn Ihre Basisklasse über einen Finalize-Wert verfügt.

Der Grund, Finalizer zu vermeiden, sofern Sie sie nicht unbedingt benötigen, besteht darin, dass Objekte mit Finalizern viel länger zum Garbage Collect benötigen, als Objekte ohne, also nicht Finalize "nur um sicher zu sein". Bevor das Objekt, das eine Finalisierung benötigt, mit Müll gesammelt werden kann, muss es zuerst in einer Finalisierungswarteschlange platziert werden, in der ein Hintergrundthread den Finalizer aufruft. Erst nach Abschluss der Finalisierung kann der Müll gesammelt werden, sodass für fertige Objekte immer mindestens zwei Garbage Collection-Zyklen erforderlich sind. Stellen Sie daher sicher, dass Sie die Finalisierung tatsächlich durchführen müssen, da das Fertigstellen von Objekten unnötigerweise die Leistung Ihres Programms beeinträchtigt.

Außerdem sollten Sie immer eine Dispose-Methode schreiben, die IDisposable implementiert, wenn Sie nicht verwaltete Ressourcen freigeben müssen. Es gibt ein Standardentwurfsmuster dafür in der .NET Framework-Dokumentation, und wir werden es später näher erläutern. Die Regel lautet also: Wenn Ihre Klasse nicht verwaltete Ressourcen verwaltet, müssen Sie unbedingt sowohl Finalize (oder einen Destruktor in C#) als auch Dispose nach dem Standardentwurfsmuster implementieren.

Wenn Sie eine Klasse verwenden, die IDisposable implementiert, müssen Sie Dispose aufrufen (mit der using-Anweisung in C# oder mit einem geeigneten Try/Finally-Block in Visual Basic .NET – weitere Informationen finden Sie unter Dr. GUI .NET 1.1 #3), sobald Sie mit dem Objekt fertig sind. Die Tatsache, dass der Klassenautor IDisposable implementiert hat, ist ein klarer Hinweis darauf, dass die Klasse eine nicht verwaltete Ressource steuert. Daher ist es wichtig, so schnell wie möglich nach sich selbst zu sauber. Darüber werden wir als Nächstes sprechen. Sie können auch im vorherigen Artikel ein Beispiel für das ordnungsgemäße Aufrufen von Dispose finden. (Sehen Sie sich die Zeichnungsmethoden an.)

Finalizer und Ausnahmen

Wenn Finalize oder eine Außerkraftsetzung von Finalize eine Ausnahme auslöst, ignoriert die Runtime die Ausnahme, beendet die Finalize-Methode und setzt den Finalisierungsprozess fort.

Finalisieren und C#-Destruktoren

Die C#-Syntax für Finalizer ähnelt der C++-Syntax– sie werden als "Destruktoren" bezeichnet und werden sogar wie in C++ benannt: der Klassenname, dem eine "~" (Tilde) vorangestellt ist.

Obwohl dies wie ein C++-Destruktor aussieht, ist es im Grunde nur "syntaktischer Zucker" für die Finalize-Methode . Tatsächlich generiert ein C#-Destruktor eine Finalize-Methode mit einem wichtigen Unterschied: Er generiert auch einen Aufruf der Finalize-Methode der Basisklasse am Ende der -Methode (wenn Ihre Basisklasse über eine Finalize-Methode verfügt). Auf diese Weise wird ihre Finalisierung ausgeführt, bevor Sie den Finalizer Ihrer Basisklasse aufrufen – das Gegenteil von Der Konstruktionsreihenfolge.

In C# dürfen Sie keine Finalize-Methode schreiben. Die einzige Option besteht darin, einen Destruktor zu schreiben. Das bedeutet, dass Sie nicht daran denken müssen, finalize Ihrer Basisklasse aufzurufen.

Auch wenn Sie eine andere Sprache als C# verwenden, müssen Sie je nach Sprache, die Sie verwenden, wahrscheinlich daran denken, die Finalize-Methode Ihrer Basisklasse (sofern vorhanden) am Ende Ihrer Finalize-Methode aufzurufen. Wenn Sie es vergessen, können Sie Ressourcen aus Ihren Basisklassen verloren gehen.

Denken Sie daran: Schreiben Sie keinen Destruktor oder Finalizer in einer .NET-Sprache, es sei denn, der von Ihnen entworfene Typ erfordert dies unbedingt. Dr. GUI kann dies nicht genug betonen.

Nicht deterministisch

Wenn Sie an C++-Destruktoren gewöhnt sind, werden Sie von diesem interessanten Aspekt der .NET-Finalizer enttäuscht sein: Sie können nicht genau wissen, wann sie aufgerufen werden.

Insbesondere werden sie nicht lange aufgerufen, bevor das Objekt mit dem Gesammelten Garbage erfasst wird. Wann wird das sein? Wenn das System darauf zugeht. Sie können sie bitten, eine Garbage Collection zu machen, aber das ist in der Regel nicht das, was Sie tun möchten – und es ist nicht garantiert, dass alle Finalisierer auf jeden Fall sofort aufgerufen werden.

Schreiben von Finalizern und "Dispose"

Was ist also, wenn Ihr Objekt eine kritische und teure Ressource steuert, die sofort bereinigt werden muss, z. B. eine Datenbankverbindung oder ein Dateihandle? Die richtige Antwort besteht darin, sich nicht auf Finalize zu verlassen. Stattdessen sollten Sie eine Dispose-Methode schreiben, um IDisposable zu implementieren, und die Verantwortung für den Aufruf übernehmen, wenn Sie mit Ihrem Objekt fertig sind.

Die Dispose-Methode sollte zuerst überprüfen, ob sie bereits aufgerufen wurde. Wenn dies nicht der Fall ist, sollte es die erforderliche Bereinigung durchführen und dann Base aufrufen. Dispose()/MyBase.Dispose(), wenn Ihre Basisklasse über eine Dispose-Methode verfügt. Schließlich sollte Ihre Dispose-MethodeSystem.GC.SuppressFinalize aufrufen, damit das Objekt den Finalisierungsschritt in der Garbage Collection überspringen kann (wir gehen davon aus, dass Sie wissen, dass es sicher ist, die Finalisierung zu überspringen, da Sie Dispose ordnungsgemäß in Ihrer Klasse und ihren Basisklassen entworfen haben).

Wir schreiben auch einen Finalizer, um sicherzustellen, dass wir sauber, auch wenn unser Benutzer vergisst, Dispose aufzurufen. Die Finalize-Methode sollte Dispose aufrufen, um die von Ihnen benötigte Bereinigung durchführen zu können. Rufen Sie dann Finalize Ihrer Basisklasse auf, falls vorhanden. (Der Basisklassenaufruf erfolgt automatisch im Destruktor von C#.)

Wenn eine Möglichkeit besteht, dass Dispose eine Ausnahme auslöst, verwenden Sie unbedingt eine try/finally-Anweisung mit dem Aufruf von base. Finalize()/MyBase.Dispose() im finalen Block, sodass die Finalisierung Ihres Objekts abgeschlossen wird.

Ausführlichere Informationen und Beispielcode in C# und Visual Basic .NET finden Sie im Entwurfsmuster für Finalizer und Dispose im .NET Framework SDK.

Nicht absolut garantiert

Zusätzlich zu dem Problem, das Sie nicht wissen, wann der Finalizer aufgerufen wird, ist nicht garantiert, dass Finalize überhaupt aufgerufen wird. Insbesondere kann die Finalize-Methode unter den folgenden außergewöhnlichen Umständen nicht bis zum Abschluss oder gar nicht ausgeführt werden:

  • Ein weiterer Finalizer blockiert unbegrenzt (wechselt in eine Endlosschleife, versucht, eine Sperre zu erhalten, die nie abgerufen werden kann usw.). Da die Runtime versucht, Finalizer bis zur Vervollständigung auszuführen, werden andere Finalizer möglicherweise nicht aufgerufen, wenn ein Finalizer unbegrenzt blockiert wird.
  • Der Prozess wird beendet, ohne der Runtime die Möglichkeit zu geben, sauber. In diesem Fall ist die erste Benachrichtigung der Runtime über die Beendigung des Prozesses eine DLL_PROCESS_DETACH Benachrichtigung.

Die Laufzeit beendet Objekte nur während des Herunterfahrens, während die Anzahl der finalisierbaren Objekte weiterhin abnimmt.

ToString

Die ToString-Methode gibt einen String-Wert zurück, der eine kulturabhängige Zeichenfolgendarstellung Ihres Objekts enthält. Mit kultursensitiv meinen wir, dass die aktuelle Kultur (für instance, Englisch (US)) den Rückgabewert von ToString beeinflussen kann. Kann beispielsweise 1.23.ToString() "1,23" in der USA, aber "1,23" in Europa zurückgeben.

Die Implementierung von ToString in System.Object gibt den vollqualifizierten Typnamen Ihres Objekts zurück.

Die integrierten Werttypen überschreiben ToString, um eine kultursenstive Zeichenfolgendarstellung ihrer Daten zurückzugeben – für instance, Int32, DateTime oder die meisten beliebigen Typen. ToString gibt eine Zeichenfolgendarstellung des im -Objekt gespeicherten Datums und der Uhrzeit zurück. Beispiel:

   New DateTime(2001, 2, 1, 3, 24, 56).ToString()

ergibt die Zeichenfolge, die enthält the date and time in the appropriate format for the current culture.

Es ist sehr üblich, ToString in Ihren eigenen Klassen zu überschreiben, um die gewünschte Zeichenfolgendarstellung zurückzugeben. Für instance kann eine Punktklasse die koordinaten zurückgeben, die durch Kommas getrennt und durch Klammern eingeschlossen sind, z"(2, 3)". B. . Es gab ein Beispiel für das Überschreiben von ToString in der Diskussion über Polymorphismus, Virtual und Overrides in Dr. GUI .NET 1.1 #2.

MemberwiseClone

MemberwiseClone gibt ein neues Objekt zurück, das ein Member-by-Member-Duplikat des ursprünglichen Objekts ist. Dieser Kopiertyp wird als flache Kopie bezeichnet. Die enthaltenen Objekte, auf die die Verweise des Objekts verweisen, werden nicht kopiert. Daher verweisen sowohl das ursprüngliche Objekt als auch das geklonte Objekt auf dieselben enthaltenen Objekte.

MemberwiseClone ist geschützt, sodass es nur von einer Methode des Objekts aufgerufen werden kann, das es klont. Und es ist nicht virtuell/überschrieben, sodass es nicht überschrieben werden kann.

Sie wird hauptsächlich als Hilfsfunktion für Ihre eigene Clone-Methode verwendet. Dies ist praktisch, da es Ihr Objekt klont, einschließlich des Basisklassenteils Ihres Objekts, ohne dass der abgeleitete Code die Details der Implementierung kennen muss.

Wenn Sie Benutzern das Klonen von Objekten Ihrer Klasse ermöglichen möchten, implementieren Sie die ICloneable-Schnittstelle , einschließlich der Clone-Methode . Wenn Sie nur eine flache Kopie möchten (d. h. Sie kopieren Verweise, aber nicht die enthaltenen Objekte, auf die diese Verweise verweisen; das geklonte Objekt zeigt auf dieselben enthaltenen Objekte wie das Original), rufen Sie einfach MemberwiseClone auf. Wenn Sie die enthaltenen Objekte kopieren möchten, können Sie die Member entweder selbst kopieren oder MemberwiseClone aufrufen. Kopieren Sie dann die Unterobjekte, auf die die Verweise des alten Objekts verweisen, und stellen Sie fest, dass die Verweise des neuen Objekts auf die kopierten Unterobjekte verweisen. Das Kopieren der gesamten Datenstruktur, auf die ein Objekt verweist, wird als tiefgehende Kopie bezeichnet.

GetType

Die GetType-Methode gibt einen Verweis auf das Type-Objekt Ihres Objekts zurück. Mit dem Type-Objekt können Sie auf eine vollständige Beschreibung der Metadaten Ihres Objekts zugreifen, einschließlich Feldern, Methoden, Ereignissen, Eigenschaften und Konstruktoren der Klasse, sowie Parametern und Rückgabetypen aller vorherigen sowie Informationen zu dem Modul und der Assembly, die die Klasse enthalten. Beachten Sie, dass diese Metadaten Informationen zu allen Mitgliedern enthalten, einschließlich privater Mitglieder. Daher ist es keine Sicherheitsmaßnahme, ein Mitglied privat zu kennzeichnen, da jeder über die Metadaten auf jedes Element zugreifen kann, wenn nicht direkt. Dieser Zugriff umfasst die Möglichkeit, ein privates Feld abzurufen oder festzulegen und eine private Methode aufzurufen. Wenn Sie Sicherheit benötigen, verwenden Sie einen tatsächlichen Sicherheitsmechanismus, z. B. Codezugriffssicherheit.

Die Metadaten werden als Netzwerk von Sammlungen von typisierten Objekten dargestellt. Für instance gibt es eine Auflistung von MethodInfo-Objekten, die Informationen zu jeder Methode in Ihrer Klasse enthält. Tatsächlich enthält das MethodInfo-Objekt sogar eine Invoke-Funktion , mit der Sie die -Methode aufrufen können. (Im gleichen Token enthält das FieldInfo-Objekt Methoden zum Abrufen und Festlegen des Feldwerts.)

Für jeden typ gibt es genau ein Type-Objekt in der Anwendungsdomäne, das ihn beschreibt. Das bedeutet, wenn zwei Objekte denselben Wert von GetType zurückgeben, sind diese Objekte vom gleichen Typ – und wenn sich die Werte unterscheiden, sind die Objekte von unterschiedlichen Typen.

Die Type-Klasse enthält auch einige interessante statische Methoden, mit denen Sie ein Type-Objekt abrufen (oder erstellen können, wenn es noch nicht vorhanden ist). Für instance verwendet die statische Type.GetType-Methode eine Zeichenfolge, die den vollqualifizierten Typnamen enthält. Sie rufen dies auf, um das Type-Objekt abzurufen, das diesem Namen entspricht. Sobald Sie über dieses Type-Objekt verfügen, können Sie Activator.CreateInstance aufrufen, um eine instance dieses Typs zu erstellen. An diesem Punkt können Sie herausfinden, welche Methoden von diesem Typ implementiert werden, und sie mithilfe von MethodInfo.Invoke aufrufen oder das Objekt anderweitig bearbeiten.

Später sehen Wir ein Beispiel für das dynamische Erstellen eines Objekts. Der gute Arzt glaubt, dass Sie davon begeistert sein werden.

Equals (Instanzmethode)

Wert und Verweisgleichheit

Die Diskussion von Equals (und dem eng verwandten GetHashCode) ist in jeder Sprache mit Verweisen ziemlich schwierig. Sollte der Gleichheitsoperator nur true zurückgeben, wenn die beiden Verweise auf dasselbe Objekt verweisen? Dies wird als Verweisgleichheit oder Identität bezeichnet.

Oder sollte true zurückgegeben werden, wenn die beiden Objekte denselben Wert enthalten, unabhängig davon, ob die Verweise auf zwei unterschiedliche Objekte verweisen? Dies wird als Gleichheit oder, genauer gesagt, Wertgleichheit bezeichnet.

Die Implementierung von Equals in System.Object verwendet Verweisgleichheit. Dies ist der einzige Gleichheitstyp, der garantiert für alle Objekte gültig ist. (Theoretisch ist der Vertrag für Equals für die Wertgleichheit vorgesehen, aber es ist im allgemeinen Fall für Verweistypen sehr schwierig zu implementieren, sodass die .NET Framework stattdessen standardmäßig Verweisgleichheit verwendet.) Equals gibt also true zurück, wenn die beiden Verweise (dieser und der Parameter) identisch sind. Anders ausgedrückt: Wenn sich die beiden Verweise auf genau dasselbe Objekt beziehen, wissen wir sicher, dass sie gleichwertig sind.

Werttypen wie Int32 verwenden Wertgleichheit: Wenn alle Felder des Objekts gleich sind, werden die Objekte als gleich angesehen. Die Basisimplementierung von Equals für Werttypen befindet sich in System.ValueType. Beachten Sie, dass alle Werttypen in der .NET Framework Klassenbibliothek von System.ValueType abgeleitet werden. (In C# verwenden Sie eine Struktur anstelle einer Klasse , um einen Werttyp zu deklarieren. In Visual Basic .NET ist es Struktur und nicht Klasse.) Beachten Sie weiter, dass viele Werttypen Equals überschreiben, um eine höhere Effizienz zu erzielen. Wenn Sie sich den IL-Code für System.ValueType.Equals ansehen, sehen Sie eine ziemlich komplizierte Methode. Es ist ziemlich einfach, eine zu schreiben, die schneller ist.

In den meisten Verweistypen sollten Sie im Allgemeinen keine Gleichheiten überschreiben. Für Verweistypen ist die Verweisgleichheit in der Regel geeignet. Die Ausnahme von dieser Regel ist, wenn Ihr Verweistyp wie ein Basistyp (oder wert) aussieht, z. B. String, Point, Complex, BigNumber usw.

Wenn Ihre Klasse ein Werttyp ist, ist der integrierte Wertgleichheitsoperator möglicherweise ausreichend, obwohl es in Ordnung ist , Equals für eine höhere Effizienz zu überschreiben.

Beachten Sie, dass Sie beim Überschreiben von Equals auch GetHashCode überschreiben müssen (wie im Nächsten gezeigt). Außerdem sollten Sie erwägen, die IComparable-Schnittstelle zu implementieren, damit Ihre Objekte in geordneten Datensammlungen verwendet werden können.

Eine der offensichtlichsten Ausnahmen von der Regel zur Verwendung der Verweisgleichheit für Verweisklassen ist die Klasse String. (Zeichenfolgen sind in vielerlei Hinsicht außergewöhnlich, oder?) Obwohl Zeichenfolgen Verweistypen sind, erwartet jeder Programmierer, dass sie über eine Wertgleichheitssemantik verfügen. Für instance wird erwartet, dass Folgendes wie in den Kommentaren beschrieben funktioniert:

[Visual Basic .NET]
   Dim a as String = "Hello"
   Dim b as String = String.Copy(a)
   Console.WriteLine("{0}: a.Equals(b)", a.Equals(b)) ' true
   Console.WriteLine("{0}: a == b", a = b) ' true
   Console.WriteLine("{0}: a and b are the same string", _
      a Is b) '// false
   Console.WriteLine("{0}: a and b are the same string using RefEq", _
      Object.ReferenceEquals(a, b)) ' still false :)

[C#]
   String a = "Hello";
   String b = String.Copy(a);
   Console.WriteLine("{0}: a.Equals(b)", a.Equals(b)); // true
   Console.WriteLine("{0}: a == b", a == b); // true
   Console.WriteLine("{0}: a and b are the same string", 
      (Object)a == (Object)(b)); // false
   Console.WriteLine("{0}: a and b are the same string using RefEq", 
      Object.ReferenceEquals(a, b)); // still false :)

Beachten Sie, dass wir die String-Objekte in der letzten WriteLine in Object umwandeln (in Visual Basic .NET haben wir einfach den Is-Operator verwendet), sodass wir den integrierten Operator == für Verweise verwenden, der auf Gleichheit verweist. Sie können diesen Trick jedes Mal verwenden, wenn Sie Verweisgleichheit erreichen möchten. Dies ist besonders nützlich, wenn Sie mit NULL vergleichen. Außerdem verwenden wir die static/Shared-Methode Object.ReferenceEquals, um zu ermitteln, ob die beiden Verweise identisch sind. Da dies nur ein Aufruf einer .NET Framework-Methode ist, funktioniert sie in beiden Sprachen gleich, sodass Dr. GUI die Verwendung von Object.ReferenceEquals bevorzugt.

Beachten Sie, dass, wenn String.Equals und String.operator == Verweisgleichheit verwenden, wie es andere Verweistypen in der Regel tun, das vorherige Codebeispiel nicht wie erwartet funktionieren würde, da a und b nicht auf genau dieselbe Zeichenfolge im Arbeitsspeicher verweisen. Da String jedoch Wertgleichheit verwendet, funktioniert das vorherige Codebeispiel tatsächlich wie erwartet.

All dies ist ein langer Weg zu sagen, dass es einige Fälle gibt, sogar für Verweistypen, in denen Sie Equals in Ihrer Klasse überschreiben möchten. Wenn Sie dies tun, sollten Sie getHashCode auch wirklich, wirklich überschreiben, wie als nächstes beschrieben.

Wenn Sie Equals überschreiben, sollte Ihre Implementierung wie folgt sein:

  • Typsicher: Wenn der Parameter nicht Ihr Typ ist, sollten Sie false zurückgeben. Zwei Objekte unterschiedlicher Typen können nicht gleich sein.
  • Reflexiv: x.Equals(x) muss true für alle Werte von x zurückgeben.
  • Symmetrisch: x.Equals(y) muss den gleichen Wert wie y.Equals(x) für alle Werte von x und y zurückgeben.
  • Transitiv: Wenn x.Equals(y)true und y.Equals(z)true ist, muss x.Equals(z) für alle Werte von x, y und z true sein.
  • Konsistent: x.Equals(y) muss denselben Wert konsistent zurückgeben, es sei denn, x oder y werden geändert.
  • Ungleich NULL: x.Equals(null) gibt false zurück, wenn x != null.

Sie sollten sich die Richtlinien für die Implementierung der Equals-Methode ansehen.

Gleich im Vergleich zum C#-Operator ==

Wenn Sie in C++ oder C# programmieren, haben Sie bemerkt, dass es einen integrierten Operator gibt, der ähnlich aussieht wie Equals: der == Operator. Diese beiden sind nicht identisch.

Equals ist eine Methode. Der == Operator ist in die Sprachen C und C# integriert. In den meisten Objekten gibt es dafür keine Methode. Stattdessen generiert die Sprache den entsprechenden Vorgang, um den == Operator zu implementieren. Für instance generiert er die IL für den Verweisvergleich für Object, die IL für einen Wertvergleich für ganze Zahlen und einen Aufruf von String.Equals für Strings.

Jetzt ist es möglich, Operatoren in C# und C++ zu überladen. Wenn Sie also Ihre Klasse für C#- und/oder C++-Programmierer schreiben, sollten Sie einen überladenen Operator == für Ihre Klasse bereitstellen. Im Allgemeinen sollten Sie dies für Werttypen tun, aber in der Regel nicht für Verweistypen, auch wenn der Verweistyp Gleich überschreibt. Beachten Sie, dass Sie als Überladungsoperator == in C# die entsprechend benannte Methode für die Verwendung in Sprachen bereitstellen sollten, die keine überladenen Operatoren unterstützen.

Wenn Sie operator ==nicht überladen, führt der Compiler für alle Verweistypen außer String einen Verweisvergleich durch. Beachten Sie, dass equals vom Compiler im Allgemeinen nicht aufgerufen wird. Daher ist die Verwendung == in der Regel schneller als das Aufrufen von Equals, wodurch der Compiler gezwungen wird, den Methodenaufruf zu generieren.

Sie müssen GetHashCode nicht unbedingt überschreiben, nur weil Sie operator ==überladen. Wenn Sie operator ==überladen, sollten Sie jedoch auch überlegen, ob Equals überschrieben werden soll. Programmierer erwarten, dass sich Equals und operator == auf die gleiche Weise verhalten, insbesondere wenn sie Ihre Objekte in .NET Framework Datencontainern wie ArrayLists und HashTablesablegen, die Equals verwenden. Wenn Sie Equals überschreiben, sollten Sie auch GetHashCode überschreiben, damit Objekte Ihrer Klasse als Schlüssel für eine Hashtabelle verwendet werden können.

static/Shared Equals

Zusätzlich zur instance Equals-Methode gibt es eine statische/freigegebene Version von Equals. Die folgenden beiden Aufrufe sind nahezu identisch, vorausgesetzt, a und b sind derselbe Typ:

  • Instanz: a.Equals(b)
  • static/Shared: Object.Equals(a, b)

Der Unterschied besteht darin, dass die statische/freigegebene Version testet, um sicherzustellen, dass a und b vor dem Aufruf a.Equals(b)nicht NULL/Nothing sind. Daher ist es besser geeignet, zu verwenden, wenn ein möglicherweise NULL/Nothing ist.

Beachten Sie jedoch, dass es, wie dokumentiert, Außerkraftsetzungen von Equals aufruft , wenn Sie es überschrieben haben. Wenn Sie an der Dokumentation zweifeln, sehen Sie sich die aktuelle Implementierung in ILDASM an.

ReferenceEquals

ReferenceEquals ist eine statische/Shared-Methode , die immer verweisgleich ist. Daher werden die beiden Verweise verglichen und nur dann true zurückgegeben, wenn sie gleich sind. Wenn Sie die Verwendung der Verweisgleichheit erzwingen möchten, verwenden Sie ReferenceEquals wie im obigen Beispiel.

GetHashCode

GetHashCode ist die letzte der Methoden, die wir besprechen werden. Die Verwendung ist sehr begrenzt: Die Aufgabe besteht darin, einen Int32-Hashcode zurückzugeben, damit das Objekt von der HashTable-Klasse als Schlüssel verwendet werden kann.

Die Implementierung in Object.GetHashCode gibt einen eindeutigen Hashcode für jedes Objekt zurück. Dies funktioniert in Ordnung (aber nicht gut), wenn Ihr Objekt referenzgleiche Semantik verwendet.

Wenn Sie Jedoch Equals überschreiben, müssen Sie auch GetHashCode überschreiben. Andernfalls verwendet jemand irgendwann Ihre Objekte als Schlüssel in einer Hashtabelle, und ihr Code wird unterbrochen, da die HashTable-Klasse zwei Objekte mit unterschiedlichen Hashcodes vergleicht, obwohl Equalstrue zurückgibt.

Das Schreiben guter Hashfunktionen ist eine Kunst, keine Wissenschaft, und es ist etwas kompliziert, in dieser Spalte ausführlich zu erklären. (Weitere Informationen finden Sie in einem guten Informatikbuch über Datenstrukturen. Viele enthalten Kapitel zu Hashtabellen und Funktionen.) Gemäß der Dokumentation für Object.GetHashCode:

Hashfunktionen sind in der Regel für jeden Typ spezifisch und sollten mindestens eines der instance Felder als Eingabe verwenden. Eine Hashfunktion verfügt über zwei interessante Eigenschaften:

  • Wenn zwei Objekte desselben Typs denselben Wert darstellen, muss die Hashfunktion für jedes Objekt den gleichen konstanten Wert zurückgeben.
  • Eine Hashfunktion sollte eine zufällige Verteilung für alle Eingaben generieren, um die beste Leistung zu erzielen.

Hashfunktionen sollten auch sehr schnell ausgeführt werden, da sie jedes Mal aufgerufen werden, wenn Sie in der Tabelle nachschlagen (sowie jedes Mal, wenn Sie ein Schlüssel-Objekt-Paar einfügen).

Beachten Sie auch, dass die als Schlüssel verwendeten Objekte unveränderlich sein sollten. Wenn sie sich ändern, ändert sich der Wert von GetHashCode , und Sie können den Schlüssel nicht in der Hashtabelle finden, da er mit dem alten Wert gespeichert wurde. Es ist eine gute Idee, dass die Typen vertragsgemäß unveränderlich sind (d. h. es gibt keine Methoden, die den Wert des Objekts ändern), aber es ist in Ordnung, veränderliche Objekte zu verwenden, wenn Sie sehr sicher sind, dass Sie die in der Hashtabelle gespeicherten Schlüssel nach dem Speichern nicht ändern.

Sie müssen diese Dokumentation vollständig lesen und Ihre Daten gut verstehen, bevor Sie einen GetHashCode schreiben können. Leider sprengt die Bereitstellung der Informationen, die Sie benötigen, um dies gut zu tun, den Rahmen dieser Spalte.

Laufzeittypinformationen: Type und GetType

Wie bereits erwähnt, ist das von Object.GetType zurückgegebene Type-Objekt sehr leistungsfähig. Tatsächlich können Sie dieses Objekt verwenden, um Objekte des entsprechenden Typs zu erstellen und eine seiner Methoden oder get/ set-Felder , einschließlich privater Member und Felder, aufzurufen.

Im folgenden Kurzprogramm werden Sie beispielsweise aufgefordert, den Namen eines Typs und den Namen einer Methode in diesem Typ einzugeben. Anschließend wird in drei Zeilen das Type-Objekt erstellt, eine instance dieses Objekts erstellt und die -Methode aufgerufen. Und der Typ und die Methode basieren auf den Zeichenfolgen, die Sie eingeben!

Hier sehen Sie den Konsolencode in C# (oder Sie sehen die gesamte Quelldatei):

using System;
using System.Reflection;

// the first type for dynamic creation, with a HiThere method
class Type1 {
   public String HiThere() {
      return "Type1: Hi there!";
   }
}

// the second type for dynamic creation, with HiThere and
//    HelloWorld methods
class Type2 {
   public String HiThere() {
      return "Type2: Hi there!";
}

   public String HelloWorld() {
      return "Type2: Hello, world!";
   }
}

class TheApp {
   public static void Main() {
      // get names of type and method
      Console.Write("Type name: Type1 or Type2 ");
      String typeName = Console.ReadLine();

      Console.Write(
         "Method name: HiThere or HelloWorld (Type2 only) ");
      String methodName = Console.ReadLine();

      // create the Type object      
      Type t = Type.GetType(typeName);

      // create an instance of that type
      Object o = Activator.CreateInstance(t);

      // call the requested method
      t.GetMethod(methodName).Invoke(o, null);
   }
}

Und hier ist der Code für die Konsolenanwendung in Visual Basic .NET (oder Sie sehen die gesamte Quelldatei):

' Note: If you run this in a Visual Studio VB Console App project,
' VS will insert a namespace for you, causing creation of the type to
' fail unless you type the name of the namespace. To allow this program
' to work, right click on the project in Solution Explorer, then select
' Properties. The "Root namespace" box will contain your project name.
' Erase it and rebuild your project.

' The first type for dynamic creation, with a HiThere method
Class Type1
    Public Function HiThere() As String
        Return "Type1: Hi there!"
    End Function
End Class

' The second type for dynamic creation, with HiThere and
'    HelloWorld methods
Class Type2
    Public Function HiThere() As String
        Return "Type2: Hi there!"
    End Function

    Public Function HelloWorld() As String
        Return "Type2: Hello, world!"
    End Function
End Class

Class DynamicExecution
    Public Shared Function InvokeStringVoid( _
         ByVal typeName As String, ByVal methodName As String)
        ' Create the Type object      
        Dim t As Type = Type.GetType(typeName)

        ' Create an instance of that type
        Dim o As Object = Activator.CreateInstance(t)

        ' Call the requested method
        Dim s As String
        Try
            s = CType( _
                t.GetMethod(methodName).Invoke(o, Nothing), String)
        Catch e As System.Exception
            s = "Exception thrown"
        End Try
        Return s
    End Function
End Class

Class TheApp
    Public Shared Sub Main()
        ' Get names of type and method
        Console.Write("Type name: Type1 or Type2 ")
        Dim typeName As String = Console.ReadLine()

        Console.Write( _
            "Method name: HiThere or HelloWorld (Type2 only) ")
        Dim methodName As String = Console.ReadLine()
        Dim s As String = _
            DynamicExecution.InvokeStringVoid(typeName, methodName)
        Console.WriteLine(s)
        Console.ReadLine()  ' wait for user before exiting
    End Sub
End Class

Dies ist ein sehr einfaches Beispiel, aber Sie können z. B. Parameter übergeben, den Rückgabewert aus der -Methode verwenden und Felder und Eigenschaften festlegen/abrufen.

Der Code für die ASP.NET Version enthält die ersten drei oben aufgeführten Klassen (Type1, Type2 und DynamicExecution) sowie einen Ereignishandler für die Schaltfläche, der die Textfelder liest, den Inhalt überprüft und die Methode ausführt, wenn der Inhalt des Textfelds in Ordnung ist.

Hinweis Der Validierungsschritt ist für Webanwendungen von entscheidender Bedeutung . Wenn Sie dies nicht tun, öffnen Sie eine große Sicherheitslücke. Jeder kann dazu führen, dass Ihr Server eine .NET Framework-Methode ausführt, die keine Parameter akzeptiert und eine Zeichenfolge zurückgegeben hat! Tue nicht!Immer, immer, Immerüberprüfen Sie Eingaben, die über das Internet kommen!

Tatsächlich müssten wir die Eingaben im Code überprüfen, selbst wenn wir die Eingabe des Benutzers auf der Webseite eingeschränkt hätten (z. B. durch Verwendung eines Paars von Dropdownmenüs anstelle von Textfeldern), da jemand, der unser System hacken wollte, den Benutzereingabeteil "überspringen" und die HTTP-Übermittlung fälschen könnte.

Hier sehen Sie den Ereignishandler für die Schaltfläche. Wenn Sie diesen Code auf Ihrem Computer ausführen, müssen Sie den Namespacenamen ändern, wenn er sich von dem des guten Arztes unterscheidet. Sie können sich diese Anwendung hier ansehen, und Sie können den vollständigen Code sehen.

Private Sub Button1_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles Button1.Click
    Dim typeName As String = TextBox1.Text
    Dim methodName As String = TextBox2.Text
    If (typeName <> "Type1" And _
            typeName <> "Type2") Or _
            (methodName <> "HiThere" And _
            methodName <> "HelloWorld") Then
        Label1.Text = "Bad type or method name, please retype!"
    Else
        typeName = "DynamicCallASPX." + typeName ' add namespace
        Label1.Text = _
            DynamicExecution.InvokeStringVoid(typeName, methodName)
    End If
End Sub

Beachten Sie, dass bei unseren Überprüfungen nur sicher ist, dass der Typname einer der beiden gültigen Namen und der Methodenname einer der beiden gültigen Methodennamen ist. Durch diese Überprüfung kann Type1.HelloWorld durchlaufen werden. Aber das ist kein Problem, da wir wissen, dass der ungültige Name eine Ausnahme auslöst und kein Sicherheitsproblem verursacht.

Entfernen von Objekten: Ein bisschen mehr zu Garbage Collection, Finalize und Dispose

Wir haben bereits die Tatsache erläutert, dass die .NET Framework eine Garbage Collection-Laufzeitumgebung ist. Das bedeutet, dass Sie zwar weiterhin für die Zuweisung der benötigten Objekte (in der Regel durch Aufrufen von new/New) verantwortlich sind, sie jedoch nicht freigeben müssen. Das System erkennt automatisch, wenn ein von Ihnen zugeordnetes Objekt nicht mehr verwendet wird, und gibt es frei. Zwischen dem Zeitpunkt, zu dem erkannt wird, dass das Objekt nicht mehr verwendet wird, und wenn das System es freigibt, ruft das System Ihren Finalizer auf, wenn Sie es überschrieben haben.

Funktionsweise der Garbage Collection (GC)

In regelmäßigen Abständen wird Ihr Programm angehalten, um eine Garbage Collection zu erstellen. Zu diesem Zeitpunkt durchläuft das System die globalen Variablen und den Stapel, um zu bestimmen, auf welche Objekte derzeit verwiesen wird. Außerdem werden Verweise in den Objekten selbst ausgeführt, um verknüpfte Listen und andere verknüpfte Datenstrukturen ordnungsgemäß zu berücksichtigen. Die Möglichkeit, zu wissen, wo sich die Verweise in Ihren Objekten befinden, ist einer der Gründe.NET Framework Daten immer stark typisiert sind.

Die objekte, die nicht verwendet werden, werden freigegeben (wenn sie keine Finalisierung benötigen) oder in eine Liste zur Finalisierung eingefügt. Dieser zusätzliche Schritt für die Finalisierung ist der größte Grund, um das Schreiben von Finalizern zu vermeiden, die Sie nicht benötigen. Nachdem die Objekte abgeschlossen wurden, werden sie in einer zukünftigen Garbage Collection freigegeben.

Dies ist eine sehr schnelle Übersicht über .NET Framework Garbage Collection. Ausführlichere Ansichten finden Sie unter Teil 1 und Teil 2 von Jeffrey Richters hervorragenden Artikeln zu diesem Thema im MSDN Magazine. Lesen Sie auch Rico Mariani exzellenten Artikel Garbage Collector Grundlagen und Leistungshinweise.

Die Fehler-GC verhindert

Windows C++-Programmierer verfügen nicht über eine Garbage Collection. Daher sind sie anfällig für zwei Arten von Fehlern: Objekte zu früh freigeben und vergessen, sie überhaupt freizugeben. Wenn Sie ein Objekt zu früh freigeben, können Sie Methoden für das Objekt aufrufen oder seine Felder festlegen, nachdem es freigegeben wurde und nachdem ein anderes Objekt seinen Arbeitsspeicher verwendet hat. Diese Art von Fehler kann unglaublich schwer zu finden sein!

Wenn Sie vergessen, ein Objekt überhaupt freizugeben, wächst die Speicherbelegung für Ihr Programm. Wenn Sie dies in einer Schleife tun, kann sie ohne Begrenzung wachsen. Nun, in Wirklichkeit ist es begrenzt – durch die Menge des virtuellen Arbeitsspeichers auf Ihrem Computer. Das Vergessen, Objekte freizugeben, führt zu einem sogenannten Speicherverlust. Speicherverluste sind vor allem bei der Serverprogrammierung eine große Ursache für Kopfschmerzen, da die auf einem Server ausgeführten Programme unbegrenzt ausgeführt werden.

Da die .NET Framework Arbeitsspeicher für Sie freigibt (aber erst, nachdem Sie damit fertig sind), werden beide Fehler aus .NET Framework Programmen entfernt, unabhängig davon, welche verwaltete Sprache Sie verwenden.

GC erleichtert auch ein schwieriges Problem. Haben Sie jemals versucht, ein dynamisches Objekt als Verweis in C++ zurückzugeben? Beispiel:

Foo & MyFunc(int i) {
   return new Foo(i);
}
// call as in Foo f = MyFunc(5);

Es ist einfach, das Objekt nach Adresse zurückzugeben. Was schwer ist, ist, zu wissen, was danach damit zu tun ist. Es gibt keine gute Möglichkeit, das Objekt zu löschen, sodass Sie wahrscheinlich einen Speicherverlust verursacht haben. Und wenn Sie herausgefunden haben, wie Sie es löschen, sollten Sie? Wurde es zu Beginn dynamisch zugeordnet? Wird der standardmäßige Löschoperator verwendet?

Die Garbage Collection im .NET Framework übernimmt dies. Alle Objekte werden nach Verweis zurückgegeben, und alle werden automatisch bereinigt, wenn sie nicht mehr verwendet werden.

Wie GC Ihren Programmierstil ändert

Als Dr. GUI zum ersten Mal mit der Programmierung mit einem Garbage Collection-System begann, fand er es seltsam. Für einen C++-Programmierer ist es einfach falsch, Objekte zuzuweisen und sie nie freizugeben. Und da so viele Typen unveränderlich sind, erstellen Sie sie oft und werfen sie sehr schnell weg.

In einem System mit Garbage Collection ist das in Ordnung. Das System ist so konzipiert, dass Zuordnungen sehr schnell verarbeitet werden, sodass die Geschwindigkeitsstrafe gering ist. Und wenn das Objekt nicht lange verwendet wird, wird es auch so optimiert, dass es sehr schnell ist. Es ist schwer zu lernen, sich keine Sorgen darüber zu machen, dass die Zuweisung von Arbeitsspeicher teuer ist, aber Sie können es wirklich tun!

Oh, und im .NET Framework können Sie immer Werttypen verwenden, wenn Sie Heapzuordnungen ganz vermeiden möchten.

Versuch es doch mal!

Seien Sie der Erste in Ihrem Block...

Da es bei der .NET Framework darum geht, verteilte Anwendungen gut zu machen, liegt es daran, dass es keinen Spaß macht, allein zu programmieren. Laden Sie also einen oder zwei oder drei Freunde ein, mit Ihnen zu lernen. Es wird mehr Spaß machen, und Sie werden alle mehr erfahren!

Einige Dinge, die Sie ausprobieren können...

Schreiben Sie eine Punktklasse für den dreidimensionalen Raum (mit den Koordinaten x, y und z). Legen Sie dies vorerst als Verweistyp und nicht als Werttyp fest. (Zusätzliche Gutschrift: Machen Sie ihn zu einem Werttyp, nachdem Sie ihn als Verweistyp verwendet haben.)

Überschreiben Sie in Ihrer Point-Klasse ToString, Equals und GetHashCode. Fügen Sie beliebige andere Methoden hinzu (einschließlich möglicherweise überladener Operatoren).

Implementieren Sie als Nächstes in Ihrer Point-Klasse die ICloneable-Schnittstelle , damit Sie Ihr Objekt kopieren können, indem Sie Clone aufrufen. Wenn Sie möchten, implementieren Sie Clone in zwei identischen Klassen, indem Sie MemberwiseClone aufrufen und die Felder zuweisen. sehen Sie dann, welche schneller ausgeführt werden. (Zeit für eine Reihe von Klonvorgängen – z. B. 10.000 oder 100.000 – und verwenden Sie den Systemtimer für die Zeit.)

Sehen Sie sich die generierten Metadaten und den Code in ILDASMan.

Versuchen Sie, einige andere Objekte zu schreiben.

Verwenden Sie GetType , um die Metadaten für Ihre Objekte zu untersuchen und ein Feld festzulegen und/oder eine Methode für ein Objekt aufzurufen.

Was wir getan haben; Was kommt als nächstes

Dieses Mal haben wir die Mutter und den Vater aller .NET-Klassen besprochen: das ehrwürdige System.Object. Außerdem wurde die Speicherbelegung, die Garbage Collection und die ordnungsgemäße Entsorgung nicht verwalteter Ressourcen erläutert.

Das nächste Mal werden wir über Zeichenfolgen sprechen.