Diese Inspektion macht Sie darauf aufmerksam, dass mehr Schließwerte erfasst werden, als offensichtlich sichtbar ist, was sich auf die Lebensdauer dieser Werte auswirkt.
Betrachten Sie den folgenden Code:
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);
};
}
}
Im ersten Abschluss sehen wir, dass sowohl obj1 als auch obj2 explizit erfasst werden. Wir können dies sehen, indem wir uns den Code ansehen. Beim zweiten Abschluss können wir sehen, dass obj1 explizit erfasst wird, aber ReSharper warnt uns, dass obj2 implizit erfasst wird.
Dies liegt an einem Implementierungsdetail im C # -Compiler. Während der Kompilierung werden Abschlüsse in Klassen mit Feldern umgeschrieben, die die erfassten Werte enthalten, und Methoden, die den Abschluss selbst darstellen. Der C # -Compiler erstellt nur eine solche private Klasse pro Methode. Wenn in einer Methode mehr als ein Abschluss definiert ist, enthält diese Klasse mehrere Methoden, eine für jeden Abschluss, und enthält auch alle erfassten Werte aus allen Abschlüssen.
Wenn wir uns den Code ansehen, den der Compiler generiert, sieht er ungefähr so aus (einige Namen wurden bereinigt, um das Lesen zu erleichtern):
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);
}
}
Wenn die Methode ausgeführt wird, wird die Anzeigeklasse erstellt, die alle Werte für alle Abschlüsse erfasst. Selbst wenn ein Wert in einem der Abschlüsse nicht verwendet wird, wird er dennoch erfasst. Dies ist die "implizite" Erfassung, die ReSharper hervorhebt.
Diese Inspektion impliziert, dass der implizit erfasste Verschlusswert erst dann als Müll gesammelt wird, wenn der Verschluss selbst Müll ist. Die Lebensdauer dieses Werts ist jetzt an die Lebensdauer eines Abschlusses gebunden, der den Wert nicht explizit verwendet. Wenn der Abschluss langlebig ist, kann sich dies negativ auf Ihren Code auswirken, insbesondere wenn der erfasste Wert sehr groß ist.
Beachten Sie, dass dies zwar ein Implementierungsdetail des Compilers ist, jedoch über Versionen und Implementierungen wie Microsoft (vor und nach Roslyn) oder Monos Compiler hinweg konsistent ist. Die Implementierung muss wie beschrieben funktionieren, um mehrere Abschlüsse, die einen Werttyp erfassen, korrekt zu behandeln. Wenn beispielsweise mehrere Abschlüsse ein int erfassen, müssen sie dieselbe Instanz erfassen, was nur mit einer einzelnen gemeinsam genutzten privaten verschachtelten Klasse möglich ist. Der Nebeneffekt davon ist, dass die Lebensdauer aller erfassten Werte jetzt die maximale Lebensdauer eines Abschlusses ist, der einen der Werte erfasst.