序文: 私は問題を解決する方法を知っています. なぜ発生するのか知りたいです。質問を上から下まで読んでください。
誰もが (当然のことながら) 知っているように、イベント ハンドラーを追加すると、C# でメモリ リークが発生する可能性があります。イベント ハンドラのメモリ リークを回避する理由と方法を参照してください。
一方、オブジェクトのライフ サイクルは類似または関連していることが多く、イベント ハンドラの登録解除は必要ありません。次の例を検討してください。
using System;
public class A
{
private readonly B b;
public A(B b)
{
this.b = b;
b.BEvent += b_BEvent;
}
private void b_BEvent(object sender, EventArgs e)
{
// NoOp
}
public event EventHandler AEvent;
}
public class B
{
private readonly A a;
public B()
{
a = new A(this);
a.AEvent += a_AEvent;
}
private void a_AEvent(object sender, EventArgs e)
{
// NoOp
}
public event EventHandler BEvent;
}
internal class Program
{
private static void Main(string[] args)
{
B b = new B();
WeakReference weakReference = new WeakReference(b);
b = null;
GC.Collect();
GC.WaitForPendingFinalizers();
bool stillAlive = weakReference.IsAlive; // == false
}
}
A
イベントをB
介して暗黙的に相互参照しますが、GC はそれらを削除できます (参照カウントではなく、マークアンドスイープを使用しているため)。
しかし、次の同様の例を考えてみましょう:
using System;
using System.Timers;
public class C
{
private readonly Timer timer;
public C()
{
timer = new Timer(1000);
timer.Elapsed += timer_Elapsed;
timer.Start(); // (*)
}
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
// NoOp
}
}
internal class Program
{
private static void Main(string[] args)
{
C c = new C();
WeakReference weakReference = new WeakReference(c);
c = null;
GC.Collect();
GC.WaitForPendingFinalizers();
bool stillAlive = weakReference.IsAlive; // == true !
}
}
C
GC がオブジェクトを削除できないのはなぜですか? Timer がオブジェクトを存続させるのはなぜですか? タイマーは、タイマー機構の「隠された」参照 (静的参照など) によって維持されていますか?
(*) 注意: タイマーが作成されただけで、開始されていない場合、問題は発生しません。開始して後で停止しても、イベント ハンドラーが登録解除されていない場合、問題は解決しません。