この検査は、明らかに目に見えるよりも多くのクロージャ値がキャプチャされているという事実に注意を向けます。これは、これらの値の寿命に影響を与えます。
次のコードを検討してください。
using System;
public class Class1 {
private Action _someAction;
public void Method() {
var obj1 = new object();
var obj2 = new object();
_someAction += () => {
Console.WriteLine(obj1);
Console.WriteLine(obj2);
};
// "Implicitly captured closure: obj2"
_someAction += () => {
Console.WriteLine(obj1);
};
}
}
最初のクロージャーでは、obj1とobj2の両方が明示的にキャプチャされていることがわかります。コードを見るだけでこれを確認できます。2番目のクロージャでは、obj1が明示的にキャプチャされていることがわかりますが、ReSharperはobj2が暗黙的にキャプチャされていることを警告しています。
これは、C#コンパイラの実装の詳細によるものです。コンパイル中に、クロージャーは、キャプチャーされた値を保持するフィールドと、クロージャー自体を表すメソッドを持つクラスに書き直されます。C#コンパイラは、メソッドごとにこのようなプライベートクラスを1つだけ作成します。メソッドで複数のクロージャが定義されている場合、このクラスには複数のメソッドが含まれます(各クロージャに1つ)。また、すべてのクロージャからキャプチャされたすべての値も含まれます。
コンパイラが生成するコードを見ると、次のようになります(一部の名前は読みやすくするためにクリーンアップされています)。
public class Class1 {
[CompilerGenerated]
private sealed class <>c__DisplayClass1_0
{
public object obj1;
public object obj2;
internal void <Method>b__0()
{
Console.WriteLine(obj1);
Console.WriteLine(obj2);
}
internal void <Method>b__1()
{
Console.WriteLine(obj1);
}
}
private Action _someAction;
public void Method()
{
// Create the display class - just one class for both closures
var dc = new Class1.<>c__DisplayClass1_0();
// Capture the closure values as fields on the display class
dc.obj1 = new object();
dc.obj2 = new object();
// Add the display class methods as closure values
_someAction += new Action(dc.<Method>b__0);
_someAction += new Action(dc.<Method>b__1);
}
}
メソッドが実行されると、すべてのクロージャーについて、すべての値をキャプチャする表示クラスが作成されます。したがって、値がいずれかのクロージャで使用されていない場合でも、キャプチャされます。これは、ReSharperが強調している「暗黙の」キャプチャです。
この検査の意味は、暗黙的にキャプチャされたクロージャ値は、クロージャ自体がガベージコレクションされるまでガベージコレクションされないということです。この値の有効期間は、値を明示的に使用しないクロージャーの有効期間に関連付けられています。クロージャの寿命が長い場合、特にキャプチャした値が非常に大きい場合は、コードに悪影響を与える可能性があります。
これはコンパイラーの実装の詳細ですが、Microsoft(Roslynの前後)やMonoのコンパイラーなどのバージョンと実装全体で一貫しています。実装は、値型をキャプチャする複数のクロージャを正しく処理するために、説明どおりに機能する必要があります。たとえば、複数のクロージャがintをキャプチャする場合、それらは同じインスタンスをキャプチャする必要があります。これは、単一の共有プライベートネストクラスでのみ発生する可能性があります。これの副作用は、すべてのキャプチャされた値の有効期間が、任意の値をキャプチャするすべてのクロージャの最大有効期間になることです。