Ця перевірка звертає вашу увагу на той факт, що фіксується більше значень закриття, ніж, очевидно, помітно, що впливає на термін експлуатації цих значень.
Розглянемо наступний код:
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 явно фіксуються; ми можемо побачити це лише подивившись на код. Під час другого закриття ми можемо бачити, що obj1 явно знімається, але ReSharper попереджає нас про те, що obj2 підхоплюється чітко.
Це пов'язано з деталізацією реалізації в компіляторі C #. Під час компіляції закриття переписуються в класи з полями, що містять захоплені значення, та методи, що представляють саме закриття. Компілятор C # створить лише один такий приватний клас за методом, і якщо в методі визначено більше одного закриття, то цей клас буде містити кілька методів, по одному для кожного закриття, і він також буде включати всі захоплені значення з усіх закриттів.
Якщо ми подивимось на код, який створює компілятор, він виглядає приблизно так (деякі назви були очищені, щоб полегшити читання):
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, вони повинні захоплювати один і той же екземпляр, що може статися лише з одним спільним приватним вкладеним класом. Побічний ефект цього полягає в тому, що час життя всіх захоплених значень зараз є максимальним терміном експлуатації будь-якого закриття, яке фіксує будь-яке значення.