MSDN Magazin > Home > Ausgaben > 2007 > November >  Tiefe Einblicke in CLR: Verwalten der Objektleb...
Tiefe Einblicke in CLR
Verwalten der Objektlebensdauer
Tim Fischer

Obwohl Microsoft® .NET Framework eine verwaltete Ausführungsumgebung bereitstellt, ist es wichtig, die Ressourcen korrekt zu nutzen, die Lebensdauerverwaltung zu bedenken und Code so zu schreiben, dass diese Aspekte entsprechend berücksichtigt werden. Mit diesem Artikel sollen Entwickler unterstützt werden, die .NET- oder COM-Klassen nutzen, damit sie die Lebensdauerverwaltung besser verstehen, Situationen erkennen, in denen sie mit Ressourcenfallen konfrontiert werden können, und wissen, wie sie derartige Probleme lösen.
Beachten Sie, dass der in diesem Artikel abgebildete Code nur für Singlethreadanwendungen gilt. Wiederverwendbarer threadsicherer Code für die .NET-Ressourcenverwaltung ist im LifeTimeScope.net-Projekt unter CodePlex (siehe codeplex.com/lifetimescope) verfügbar.
In der .NET-Programmierung wird ein Objekt mit dem neuen Schlüsselwort erstellt und im verwalteten Heap zugeordnet. Das Objekt ist so lange aktiv, bis der Garbage Collector feststellt, dass das Objekt nicht mehr von einem zuverlässigen Stammverweis über einen Pfad zuverlässiger Verweise erreichbar ist. Jedes Programm verfügt über eine Main-Methode und eine zugeordnete Klasse sowie statische Klassen, die alle lokale Variablen, statische Mitglieder oder sogar Ereignisse enthalten können. Diese statischen oder lokalen Verweise werden als Stammverweise innerhalb des Programms angesehen (siehe Abbildung 1). Gewöhnliche .NET Framework-Verweise werden allgemein als zuverlässige Verweise angesehen. Die Objektlebensdauer wird vom Pfad zuverlässiger Verweise bestimmt, die von einem Stammverweis auf ein Objekt zeigen.
Abbildung 1 Zuverlässige Verweise zu Objekten im Heap 
In einigen Fällen, beispielsweise beim Integrieren eines schnellen Caches oder von Hash in ein Objektdiagramm, könnten zusätzliche Verweise zu Objekten von Interesse sein, die die Lebensdauer eines Objekts nicht ändern. Zu diesem Zweck bietet .NET Framework eine zusätzliche Klasse namens „WeakReference“, mit der Sie einen schwachen Verweis zwischen Objekten implementieren können. Dieser schwache Verweis wird beim Bestimmen der Lebensdauer eines Objekts ignoriert.
Bei Ereignissen kann es sich auch um starke Stammverweise handeln, die zum zuverlässigen Verweispfad beitragen. Dadurch wirken sie sich auf die Lebensdauer eines Objekts aus. Normale Ereignisse in CLR 2.0 (Common Language Runtime) sind bidirektionale zuverlässige Verweise zwischen der Ereignisquelle und dem Listener und können daher ein andernfalls bereits inaktives Objekt (entweder Quelle oder Listener) aktiv halten. Aus diesem Grund wurde in .NET Framework 3.0 eine WeakEvent-Klasse hinzugefügt. Sie sollten sich unbedingt mit dem noch nicht allgemein bekannten WeakEvent-Muster vertraut machen, das für das erfolgreiche Implementieren des Observer-Musters erforderlich ist. Das WeakEvent-Muster wurde in der Implementierung der WPF-Datenbindung (Windows® Presentation Foundation) verwendet, um eine Sicherheitslücke aufgrund der Datenbindung zu verhindern.

Garbage Collection und Finalization
Im Folgenden wird untersucht, wie CLR ermittelt, ob ein Objekt noch aktiv ist, und was geschieht, falls es inaktiv ist. In der Ausgabe des MSDN® Magazins vom November 2000 (msdn.microsoft.com/msdnmag/issues/1100/GCI) wird das Verfahren von Jeffrey Richter beschrieben. Jeder .NET-Prozess führt einen separaten Thread aus, der vom Garbage Collector verwendet wird. Wenn der Garbage Collector ausgeführt wird, werden alle anderen Threads angehalten. Der Garbage Collector analysiert die Speicherstrukturen und die Verweise, um herauszufinden, welche Objekte inaktiv sind. Diese Objekte werden dann für einen Prozess namens „Finalization“ in die Warteschlange eingereiht. In einer zweiten Phase wird der Finalization-Prozess im separaten Thread ausgeführt, der vom Garbage Collector verwendet wird, und die Finalizer-Methode wird für jedes Objekt aufgerufen.
Es gibt zwei wichtige Punkte, die in diesem Prozess zu beachten sind. Erstens wird der Finalizer von einem anderen Thread aufgerufen als dem Thread, der das Objekt erstellt hat. Zweitens wird der Finalizer zu einem bestimmten Zeitpunkt aufgerufen, aber nicht unbedingt zu der Zeit, zu der das Objekt tatsächlich inaktiv wurde (das wird dann deutlich, wenn das System ausgelastet, aber Speicher vorhanden ist).
Der Garbage Collector wird in der statischen System.GC-Klasse verfügbar gemacht. Sie können erzwingen, dass Garbage Collection ausgeführt wird, die inaktiven Objekte identifiziert und sie zur Finalization durch Aufrufen von GC.Collect in die Warteschlange einreiht. Sie können die Finalization auch für alle eingereihten Objekte durch Aufrufen von GC.WaitForPendingFinalizers erzwingen. Beim Aufrufen von WaitForPendingFinalizers tauchen jedoch zwei Probleme auf: Zum einen ist dies ein zeitaufwändiger Prozess, zum anderen werden die Finalizers von einem anderen Thread aufgerufen. Dies kann zu Problemen mit der Leistung und dem Threading führen, wenn Sie zum Beispiel einen Verweis an eine Systemressource freigeben müssen und dies im Finalizer durchführen möchten, denn einige nicht verwaltete Ressourcen sind threadabhängig. (Lesen Sie in diesem Fall weiter, und lernen Sie das Dispose-Muster kennen.)
Da das .NET Framework-Team den Bedarf nach einer rechtzeitigen Zerstörung von Objekten bereits erkannt hat und weiterhin erkannt hat, dass es möglich sein muss, den Zerstörungscode in einem bestimmten Thread auszuführen, hat sich das Team eine Lösung einfallen lassen, die auch als Dispose-Muster bekannt ist. Weitere Informationen erhalten Sie im Artikel „Tiefe Einblicke in CLR“ in der Ausgabe des MSDN Magazins vom Juli 2007 msdn.microsoft.com/msdnmag/issues/07/07/CLRInsideOut.
Das Dispose-Muster ist eine Konvention, die von jedem Objekt implementiert wird, das über Ressourcen verfügt und für das eine rechtzeitige Zerstörung erforderlich ist. Sie wird durch das Implementieren der IDisposable-Schnittstelle und die Freigabe von Ressourcen bei der Implementierung einer Methode namens „Dispose“ erzielt, die durch die Schnittstelle verfügbar gemacht wird. So wie auch in der nicht verwalteten Welt ist es nach wie vor die mühsame Aufgabe des Programmierers sicherzustellen, dass die Dispose-Methode zur richtigen Zeit im Clientcode aufgerufen wird. Das öffentliche Forum von Brian Harry enthält einen Beitrag, in dem erläutert wird, warum sich das Team diese Lösung ausgedacht hat und warum andere interessante Lösungen nicht erfolgreich eingesetzt werden konnten (siehe discuss.develop.com/archives/wa.exe?A2=ind0010A&L=DOTNET&P=R28572). Das Dispose-Muster muss für alle Objekte, die Ressourcen besitzen, korrekt implementiert werden.

Lebensdauerverwaltung in COM
In der COM-Welt wird die Lebensdauerverwaltung über die Verweiszählung abgewickelt. Jedes COM-Objekt verfügt über eine interne Anzahl von Clientverweisen, die darauf zeigen. Diese Zähler werden vom Cliententwickler durch Aufrufen von IUnknown::AddRef and IUnknown::Release erhöht bzw. verringert. Beachten Sie, dass der Zähler durch AddRef erhöht wird. Diese Methode sollte aufgerufen werden, wenn ein Komponentenverweis eingerichtet wird, wohingegen Release den Zähler verringert und aufgerufen werden sollte, wenn der Verweis zerstört wurde. Wenn der Zähler wieder auf 0 zurückgeht, kann das Objekt zerstört werden.
Die Aufrufe von AddRef und Release wurden durch die Verwendung von SmartPointers in der ATL (Active Template Library) oder durch Generieren des erforderlichen Codes durch den Compiler bzw. die Laufzeit, zum Beispiel in Visual Basic® 6.0, erleichtert. Ereignisse waren lediglich starke Zeiger in die eine Richtung und schwache Zeiger in die andere.
Die zwei Hauptprobleme beim Zählen der Verweise sind der Leistungsverlust und die Möglichkeit, dass inaktive Objekte, die Teil eines zyklischen Verweisszenarios sind, nicht erkannt werden können. Der Leistungsverlust ist in COM normalerweise akzeptabel, weil diese Technologie meist zur Kombination größerer Komponenten eingesetzt wird. Allerdings wird .NET Framework auf Klassenebene verwendet. Folglich wären die Auswirkungen auf die Leistung zu groß, wenn dem CLR eine Verweiszählung hinzugefügt wird.
Wenn Sie eine COM-Komponente von verwaltetem Code verwenden, generieren Sie mithilfe von tlbimp.exe einen so genannten Runtime Callable Wrapper (RCW), über den die Funktionalität der COM-Komponente der verwalteten Welt verfügbar gemacht wird. Beachten Sie, dass tlbimp.exe im Hintergrund ausgeführt wird, wenn Sie auf eine COM-Komponente in Visual Studio® verweisen. Sie können RCWs mühelos identifizieren, da sie im Debugger als System.__ComObject angezeigt werden. Es handelt sich um .NET-Typen, die einen nicht verwalteten Zeiger zum COM-Objekt enthalten und AddRef und Release für das Objekt aufrufen. AddRef wird aufgerufen, nachdem die Komponente erstellt wurde, und Release wird aufgerufen, wenn der RCW beendet ist.
Doch wann ist der RCW beendet? Das wissen Sie nicht. Deshalb benötigen Sie eine Methode, um Dispose für den RCW aufzurufen. Leider implementieren die generierten RCWs IDisposable nicht. Derzeit entspricht dem Aufruf der fehlenden Dispose-Methode der folgende Aufruf:
System.Runtime.InteropServices.Marshal.ReleaseComObject(MyCOMObject)

Die deterministische Finalization in .NET
In vielen Fällen müssen Sie sicherstellen, dass die von Ihnen verwendeten Ressourcen möglichst bald wieder freigegeben werden. Das using-Schlüsselwort in C# stellt die ideale Möglichkeit dar, um sicherzustellen, dass die Dispose-Methode für das Objekt aufgerufen wird, sobald der Programmablauf den von den geschweiften Klammern angezeigten Bereich verlässt:
using (FileStream theFileStream = File.Create("C:\\hello.txt"))
{
    string s = theFileStream.Name; 
}
Außerdem sollte beachtet werden, dass C++/CLI den Destruktor lokaler Variabler beim Beenden einer Methode automatisch aufruft. In diesen Szenarios können Sie in der Regel davon ausgehen, dass die Dispose-Methode automatisch am Ende des Blocks bzw. der Methode aufgerufen wird.
Diese Ansätze funktionieren jedoch nicht, wenn der Verwendungsbereich der Ressource nicht lokal ist. Angenommen, Sie rufen eine andere Methode im vorhergehenden Code auf, der das FileStream-Objekt als einen Eingabeparameter verwendet. Es kann sein, dass diese Methode Dispose nicht aufruft oder das using-Schlüsselwort nicht verwendet. Im Kontext eines Multithreadservers, der Ressourcen zwischen Threads freigibt, könnte das Szenario noch komplexer werden.
Leider stellt C# kein einfaches Konstrukt bereit, das dem using-Schlüsselwort entspricht und das bereichs- oder threadübergreifend verwendet werden kann. Dies macht das Wiederverwenden von Code und Routinen schwierig, da die Verantwortung für den Aufruf von Dispose zwischen Komponenten verhandelt werden muss, um sicherzustellen, dass dies nur einmal erfolgt, und zwar zum richtigen Zeitpunkt.
Vorsicht: Falls Sie Dispose nicht explizit aufrufen, übernimmt dies mit Sicherheit der Finalizer, wenn das Objekt das Dispose-Muster korrekt implementiert. Sie gehen jedoch das Risiko ein, dass bei hoher Auslastung keine Ressourcen mehr zur Verfügung stehen, weil Sie nicht wissen, wann der Finalizer aufgerufen wird. Außerdem laufen Sie Gefahr, dass der Finalizer fehlschlägt, wenn er von einem anderen Thread aufgerufen wird als dem, der das Objekt erstellt hat. Dies kann ein Problem darstellen, wenn Sie versuchen, nicht verwaltete Ressourcen oder COM-Ressourcen in einem Finalizer freizugeben.
Eine mögliche Lösung ist, eine generische Wrapperklasse um einen beliebigen Typ, der IDisposable implementiert, zu erstellen. Die Wrapperklasse gibt eine IDisposable-Schnittstelle zurück, legt intern jedoch einen Verweiszähler fest, bevor der Aufruf an die Komponente delegiert wird. Der in Abbildung 2 gezeigte Code verwendet eine neue Erweiterungsmethode namens „AddRef“, um eine entsprechende Wrapperklasse zu erhalten, die einen internen Verweiszähler für ein bestimmtes Objekt enthält.
static void Main(string[] args)
{
    FileStream theFileStream = File.Create("C:\\hello.txt");
    // Unscoped use of RefCounted
    using (theFileStream.AddRef())
    {
        a(theFileStream);
    }
          
    System.Console.Read();
}

static void a(FileStream inFileStream)
{
    // Unscoped use of RefCounted
    using (inFileStream.AddRef())
    {
        // do something
        string s = inFileStream.Name;
    }
}

Wenn AddRef aufgerufen wird, ermittelt der Code in einer Hashtabelle, ob das Objekt bereits über einen Wrapper verfügt. Falls ja, gibt die Methode den Wrapper zurück und erhöht den Zähler. Andernfalls erstellt sie einen neuen Wrapper und fügt ihn der Hashtabelle hinzu. Wenn das Programm das Ende des using-Blocks erreicht, wird automatisch Dispose aufgerufen und der Zähler verringert. Für den Fall, dass der Zähler auf null zurückgeht, wird der Dispose-Aufruf an das zugrunde liegenden Objekt weitergeleitet.
Die Implementierung der Erweiterungsmethode und der Wrapper sind in Abbildung 3 dargestellt. Der Klarheit halber muss gesagt werden, dass dieser Code nicht so entworfen wurde, dass er threadsicher ist.
public static class ReferenceCountingExtensions
{
    public static System.Collections.Generic.Dictionary<object,object> 
        theHashOfReferenceCountedClasses = 
        new System.Collections.Generic.Dictionary<object,object>();
    public static RefCounted<T> AddRef<T>(this T resource) 
        where T : IDisposable
    {
        object theObject=null;
        theHashOfReferenceCountedClasses.TryGetValue(resource, 
            out theObject);
        if (theObject == null)
        {
            theObject = new RefCounted<T>(resource);
            theHashOfReferenceCountedClasses[resource] = theObject;
        }
        else
        {
            ((RefCounted<T>)theObject).AddRef();
        }
        return (RefCounted<T>) theObject;
    }
}

// Base Class for a RefCountedUnmanagedResourceProtectingClass
public class RefCounted<T> : IDisposable
{
    private T theResource;
    bool IsFinalDisposed = false;
    int RefCounter = 0;
    public RefCounted(T inResource)
    {
        theResource = inResource;
        System.Threading.Interlocked.Increment(ref RefCounter);
    }
    public RefCounted<T> AddRef()
    {
        System.Threading.Interlocked.Increment(ref RefCounter);
        return this;
    }
    public T Resource
    {
        get
        {
            return theResource;
        }
    }
    ~RefCounted()
    {
        if (!IsFinalDisposed)
        {
            Dispose();
        }
    }
     public void Dispose()
    {
        System.Threading.Interlocked.Decrement(ref RefCounter);
        if ((RefCounter == 0) && (!IsFinalDisposed)) FinalDispose();
    }
    virtual public void FinalDispose()
    {
        ((IDisposable)theResource).Dispose();
        IsFinalDisposed = true;
    }
}

Stellen Sie sicher, dass die Finalizer wirklich bis zum Ende ausgeführt werden. Die .NET Framework-Laufzeit stellt ein Konzept namens „Constrained Execution Regions“ und das CriticalFinalizerObject bereit, die Ihnen dabei helfen können.

Finalization in einer COM-Bibliothek
Im folgenden Szenario wird ein COM-Objektmodell aus C# verwendet. Sie können jede COM-Komponente leicht durch Erstellen eines Verweises zu ihrer registrierten Typbibliothek verwenden. Angenommen, es liegt eine COM-Komponente vor, die Telefonie-API (TAPI) verfügbar macht, und diese wird in den folgenden Codezeilen verwendet. Können Sie hier einen Fehler feststellen, der sich auf die Lebensdauerverwaltung bezieht?
void foo (CTAPIApplication TAPIApplication){
    string s = TAPIApplication.Ports[0].SpeechListener.GetName();
}
Der Fehler ist, dass eine Ports-Auflistung vom Objektmodell abgerufen wurde, die ReleaseComObject-Methode jedoch nicht aufgerufen wurde. Das Gleiche gilt für SpeechListener. Sie haben vielleicht den Einwand, dass nicht unbedingt angenommen werden kann, dass Foo das ComObject freigeben sollte, und Sie haben Recht. Bei diesem Code ist nicht klar ersichtlich, wer das Objekt freigeben soll und zu welchem Zeitpunkt. Was ist hier also zu tun?
Die Lösung lautet wiederum, die AddRef Methode zu verwenden, doch Sie müssen die RefCounted-Klasse so ändern, dass sie ReleaseComObject aufrufen muss, für den Fall, dass es sich um ein ComObject handelt. In Abbildung 4 wird die verbesserte FinalDispose-Methode dargestellt. Wenn T : IDisposable außerdem aus dem Aufruf von AddRef<T> entfernt wurde, weil es keine gemeinsame Schnittstelle gibt, die IDisposable- und COM-Objekte gemeinsam verwenden, gilt Folgendes:
virtual public void FinalDispose()
{
    if(theResource.GetType().IsCOMObject)
    {
        Marshal.ReleaseComObject(theResource);
    } 

    else 
    {               
        ((IDisposable)theResource).Dispose();
    }

    IsFinalDisposed = true;
}

public static RefCounted<T> AddRef<T>(this T resource)
Jetzt können Sie den Aufruf in die Telefonie-API umschreiben, wie in Abbildung 5 dargestellt. Dieser Code ist zwar immer noch etwas umständlich, aber dafür äußerst sicher, weil die using-Anweisung sicherstellt, dass ReleaseComObject wirklich aufgerufen wird, selbst wenn einmal eine Ausnahme ausgelöst wird. Das Hinzufügen einer weiteren Hilfsklasse namens „LifeTimeScope“ und das Ändern des Rückgabetyps von AddRef verbessert zudem die Lesbarkeit des Codes:
void foo(CTAPIApplication aTAPIApplication)
{
    using(PortsCollection aPortsCollection = 
        aTAPIApplication.Ports.AddRef())
    {
        using (Port aPort = aPortsCollection[0].AddRef())
        {
            using(SpeechListener aListener = 
                aPort.SpeechListener.AddRef())
            {
                string s  = aListener.GetName()
            }
        }
    }
}

void foo (CTAPIApplication TAPIApplication)
{
    using (new LifeTimeScope())
    {
        string s = 
            TAPIApplication.Ports.AddRef()[0].AddRef()
            .SpeechListener.AddRef().GetName();
    }
}
Die LifeTimeScope-Klasse, die in Abbildung 6 dargestellt wird, dient als Hilfe beim Aufruf von Dispose für alle Objektwrapper, die innerhalb eines using-Blocks mit AddRef in Berührung kamen. Bei AddRef gab es ebenfalls einige Änderungen (siehe Abbildung 7). Diese Änderungen stellen zwar einen Fortschritt dar, doch Sie müssen den Aufruf von AddRef immer noch selbst vornehmen.
public static class ReferenceCountingExtensions
{
    static public System.Collections.Generic.Dictionary<object, object>
        theHashOfReferenceCountedClasses = 
        new System.Collections.Generic.Dictionary<object, object>();

    public static T AddRef<T>(this T resource)
    {
        object theObject=null;
        theHashOfReferenceCountedClasses.TryGetValue(resource, 
            out theObject);
        if (theObject == null)
        {
            theObject = new RefCounted<T>(resource);
            theHashOfReferenceCountedClasses[resource] = theObject;
        }
        else
        {
            ((RefCounted<T>)theObject).AddRef();
        }
        System.LifeTimeScope.Current
            .theListOfWrapperObjectsToCallDisposeOn.Add(theObject);
        return (T) resource;
    }
}

public  class LifeTimeScope : IDisposable 
{
    private static LifeTimeScope _Current = null;

    private static Stack<LifeTimeScope> theScopeStack = 
        new Stack<LifeTimeScope>(); 

    public static LifeTimeScope Current
    {
        get{ return _Current; }
    }
  
    public System.Collections.ArrayList 
        theListOfWrapperObjectsToCallDisposeOn = 
        new System.Collections.ArrayList();

    public LifeTimeScope()
    {
        _Current = this;
        theScopeStack.Push(this);
    }
  
    public void Dispose()
    {
        foreach (IDisposable theObject in 
            theListOfWrapperObjectsToCallDisposeOn)
        {
            theObject.Dispose();
        }
        _Current = theScopeStack.Pop();
    }
}


Speicherverluste
Das häufigste Problem, mit dem Sie konfrontiert werden, ist, dass Ihre Anwendung aufgrund von Ereignisabonnements Speicherverluste verzeichnet. Zum Beispiel könnten Sie ein Windows Form-Benutzersteuerelement schreiben, das das NetworkChange-Ereignis in seinem Konstruktor abonniert, etwa so:
NetworkChange.NetworkAvailabilityChanged += new 
    NetworkAvailabilityChangedEventHandler(ctrl_NetAvailChangedHandler);
Das Benutzersteuerelement und die statische NetworkAvailabilityChanged-Klasse enthalten bidirektional zuverlässige Verweise. Da es sich bei NetworkAvailabilityChanged um einen statischen Stammverweis handelt, wird das Benutzersteuerelement so lange nicht aus dem Speicher freigegeben, bis der Ereignishandler entfernt ist.
Wenn Sie ein Ereignis nicht von einem Objekt abbestellen, das die Lebensdauer seines referenzierten Objekts überschreitet – in diesem Fall NetworkAvailabilityChanged und sein referenziertes Benutzersteuerelement – dann führen Sie automatisch einen Speicherverlust herbei. Dieses Problem kommt sehr oft vor und gilt für alle Arten von Observer-Musterszenarios, einschließlich Datenbindung.
In der .NET Framework 2.0-Dokumentation steht, dass Sie das Ereignis in der Dispose-Methode des Objekts abbestellen sollten. Es empfiehlt sich außerdem sicherzustellen, dass Dispose aufgerufen wird. In .NET Framework 2.0 stellen Weltereignisse nämlich Ressourcen dar, die Sie berücksichtigen müssen. Sie können in .NET Framework 3.0 jedoch eine Lösung bereitstellen, die auf dem WeakEvent-Muster basiert.
Schließlich besteht eine weitere Möglichkeit, die Zerstörung nicht verwalteter Ressourcen zu gewährleisten, in der Verwendung der HandleCollector-Klasse, die Sie bei der gelegentlichen Ausführung von GC.Collect unterstützt. Abbildung 8 bietet ein Beispiel für das Erstellen einer Instanz von HandleCollector durch Bereitstellen von drei Parametern: Handle Name (string), Initial Threshold (int) und Maximum Threshold (int). Der anfängliche Schwellenwert ist der Punkt, an dem der Garbage Collector mit der Durchführung der Garbage Collection anfangen kann. Der maximale Schwellenwert ist der Punkt, an dem der Garbage Collector die Garbage Collection durchführen muss.
// Handle Collector offers an easy way to have garbage collection
// started once we have 30+ active (and possibly destroyed) objects

class MyClassThatForcesGarbageCollectionIf30HandlesAreActive
{
    // Create a new HandleCollector
    static readonly HandleCollector appHandleCollector = 
        new HandleCollector("ApplicationUnmanagedHandles", 5, 30);

    public MyClassThatForcesGarbageCollectionIf30HandlesAreActive()
    {
        // Increment Handle Count
        appHandleCollector.Add();
    }
    ~MyClassThatForcesGarbageCollectionIf30HandlesAreActive()
    {
        // Decrement Handle Count
        appHandleCollector.Remove();
    }
}
}

Dieser Artikel enthielt einen kurzen Überblick über die Lebensdauerverwaltung in .NET- oder COM-Klassen. Nach Lektüre des Artikels sollten Sie derartige Probleme erkennen und lösen können. Weitere Informationen erhalten Sie unter den Links in der Randleiste „Ressourcen für die Lebensdauerverwaltung“.

Senden Sie Ihre Fragen und Kommentare (in englischer Sprache) an clrinout@microsoft.com.


Tim Fischer lebt in Deutschland und ist Programmmanager für Visual Studio- und Expression-Produkte bei Microsoft. Er studierte Informatik an der Universität Stuttgart und war 12 Jahre lang als Berater für modellgesteuerte Entwicklung tätig. Tim Fischer ist der Gründer von Tangible Engineering, einem Unternehmen, das sich auf das Erstellen erstklassiger .NET-Software Factorys spezialisiert.

Page view tracker