Ich sehe es momentan leider wieder öfters – .NET Entwickler unterliegen dem Irrglauben, eigene Objekte einfach mit einem “null” aus dem Speicher räumen zu können. Leider funktioniert das nicht so einfach, es gibt hier wichtige Dinge zu beachten. “Managed” heißt leider nicht immer “sorgenfrei”.
Oftmals werden Events wild in einem Objekt registriert; vor allem die Lambdas schlagen hier gerne mal zu. Ich weiß, es ist zeitsparend und sieht auch ziemlich cool aus, aber es ist leider sehr gefährlich und unkontrollierbar. Ich habe das Ganze mal an einem Timer-Objekt sichtbar gemacht. Glaubt mir, es kann weitaus fiesere Beispiele geben: FileSystemWatcher, etc…
Beispiel #1: Klassisch. Es wird ein Event genutzt, es gibt vielleicht sogar noch eine “Stop()” Methode, aber es gibt keine Deregistrierung des Events.
public class GcCantCollectEventNotDeRegistered
{
private Timer _testTimer;
public void Start()
{
_testTimer = new Timer {Interval = 1000};
_testTimer.Elapsed += TestTimerElapsed;
_testTimer.Start();
}
static void TestTimerElapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("GcCantCollectEventNotDeRegistered: Tick!");
}
}
Beispiel #2: Modern. Es wird ein Event mit Lambdas genutzt; auch hier kann es eine “Stop()” Methode geben, aber eine Deregistrierung fehlt auch hier völlig.
public class GcCantCollectBecauseLambda
{
private Timer _testTimer;
public void Start()
{
_testTimer = new Timer {Interval = 1000};
_testTimer.Elapsed += (_, e) => Console.WriteLine("GcCantCollectBecauseLambda: Tick!");
_testTimer.Start();
}
}
In beiden Beispielen tickt die Uhr munter weiter. Der Zugriff auf das Objekt ist zu diesem Zeitpunkt aber schon gesperrt, man kommt nicht mehr ran. Es ist daher sehr wichtig, alle Objekte sauber zu entfernen und Events zu deregistrieren. Ihr habt so, gerade im Bereich Multi-Threading, viel mehr Ruhe.
public class GcCollectsBecauseTheEventIsDeRegistered : IDisposable
{
private Timer _testTimer;
~GcCollectsBecauseTheEventIsDeRegistered()
{
Dispose();
}
public void Start()
{
_testTimer = new Timer {Interval = 1000};
_testTimer.Elapsed += TestTimerElapsed;
_testTimer.Start();
}
static void TestTimerElapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("GcCollectsBecauseTheEventIsDeRegistered: Tick!");
}
public void Dispose()
{
if (_testTimer == null) return;
_testTimer.Stop();
_testTimer.Elapsed -= TestTimerElapsed;
_testTimer.Dispose();
_testTimer = null;
}
}
Ob ihr, wie ich, das IDisposable Interface implementiert, oder einfach eine “Stop()” Methode nutzt, ist dabei reine Geschmackssache
Durch IDispose kommt ihr aber in den Genuss, eure Logik mit einem “using” auszuführen und sicher zu gehen, dass aufgeräumt wird.
using (var collectsBecauseTheEventIsDeRegistered = new GcCollectsBecauseTheEventIsDeRegistered())
{
collectsBecauseTheEventIsDeRegistered.Start();
Console.ReadKey();
}
Download: Codebeispiel: Events in DotNet (620)





