Inspeksi ini menarik perhatian Anda pada fakta bahwa nilai penutupan lebih banyak ditangkap daripada yang terlihat jelas, yang berdampak pada masa pakai nilai-nilai ini.
Pertimbangkan kode berikut:
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);
};
}
}
Pada penutupan pertama, kita melihat bahwa kedua obj1 dan obj2 ditangkap secara eksplisit; kita bisa melihat ini hanya dengan melihat kodenya. Untuk penutupan kedua, kita dapat melihat bahwa obj1 sedang ditangkap secara eksplisit, tetapi ReSharper memperingatkan kita bahwa obj2 sedang ditangkap secara implisit.
Ini karena detail implementasi dalam kompiler C #. Selama kompilasi, penutupan ditulis ulang ke dalam kelas dengan bidang yang menyimpan nilai yang ditangkap, dan metode yang mewakili penutupan itu sendiri. Kompiler C # hanya akan membuat satu kelas privat per metode, dan jika lebih dari satu penutupan didefinisikan dalam suatu metode, maka kelas ini akan berisi beberapa metode, satu untuk setiap penutupan, dan itu juga akan mencakup semua nilai yang diambil dari semua penutupan.
Jika kita melihat kode yang dihasilkan oleh kompiler, tampilannya sedikit seperti ini (beberapa nama telah dibersihkan untuk memudahkan membaca):
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);
}
}
Ketika metode berjalan, itu menciptakan kelas tampilan, yang menangkap semua nilai, untuk semua penutupan. Jadi, bahkan jika nilai tidak digunakan di salah satu penutupan, itu masih akan ditangkap. Ini adalah tangkapan "implisit" yang disoroti ReSharper.
Implikasi dari inspeksi ini adalah bahwa nilai penutupan yang ditangkap secara implisit tidak akan menjadi sampah yang dikumpulkan sampai penutupan itu sendiri adalah sampah yang dikumpulkan. Masa pakai nilai ini sekarang terkait dengan masa penutupan yang tidak menggunakan nilai secara eksplisit. Jika penutupan berumur panjang, ini mungkin memiliki efek negatif pada kode Anda, terutama jika nilai yang ditangkap sangat besar.
Perhatikan bahwa sementara ini adalah detail implementasi dari kompiler, ini konsisten di seluruh versi dan implementasi seperti Microsoft (sebelum dan sesudah Roslyn) atau kompiler Mono. Implementasi harus bekerja seperti yang dijelaskan untuk menangani dengan benar beberapa penutupan yang menangkap tipe nilai. Misalnya, jika beberapa penutupan menangkap int, maka mereka harus menangkap contoh yang sama, yang hanya dapat terjadi dengan satu kelas bersarang pribadi bersama. Efek sampingnya adalah masa pakai semua nilai yang ditangkap sekarang adalah masa pakai maksimum dari penutupan apa pun yang menangkap nilai apa pun.