В одном из проектов, в которых я участвую, существует обширное использование WeakAction
. То, что класс, который позволяет сохранить ссылку на экземпляр действия, не вызывая его цель, не будет собранным мусором. Способ, которым он работает, прост, он принимает действие над конструктором и сохраняет слабую ссылку на цель действия и на метод, но отбрасывает ссылку на само действие. Когда придет время для выполнения действия, он проверяет, жив ли объект, и если да, вызывает метод на целевом сервере.
Все работает хорошо, за исключением одного случая - когда действие создается в закрытии. рассмотрим следующий пример:
public class A
{
WeakAction action = null;
private void _register(string msg)
{
action = new WeakAction(() =>
{
MessageBox.Show(msg);
}
}
}
Поскольку выражение лямбда использует локальную переменную msg
, компилятор С# автоматически генерирует вложенный класс для хранения всех переменных замыкания. Целью действия является экземпляр вложенного класса вместо экземпляра A. Действие, переданное конструктору WeakAction
, не упоминается после завершения конструктора и поэтому сборщик мусора может немедленно его утилизировать. Позже, если выполняется WeakAction
, это не сработает, потому что цель больше не жива, даже если исходный экземпляр A
жив.
Теперь я не могу изменить способ вызова WeakAction
(поскольку он широко используется), но я могу изменить его реализацию. Я думал о попытке найти способ получить доступ к экземпляру A и заставить экземпляр вложенного класса оставаться в живых, пока экземпляр A еще жив, но я не знаю, как его получить.
Есть много вопросов о том, что A
имеет какое-либо отношение к чему-либо, и предложения по изменению способа A
создают слабое действие (которое мы не можем сделать), поэтому здесь пояснение:
Экземпляр класса A
требует, чтобы экземпляр класса B
уведомлял его, когда что-то происходит, поэтому он обеспечивает обратный вызов с использованием объекта Action
. A
не знает, что B
использует слабые действия, он просто предоставляет Action
для выполнения обратного вызова. Тот факт, что B
использует WeakAction
, является деталью реализации, которая не отображается. B
необходимо сохранить это действие и использовать его при необходимости. Но B
может жить намного дольше, чем A
, и удерживая сильную ссылку на нормальное действие (которое само по себе содержит сильную ссылку на экземпляр A
, который его создал) заставляет A
никогда не собираться мусором, Если A
является частью списка элементов, которые больше не являются живыми, мы ожидаем, что A
будет собрано мусором, и из-за ссылки, что B
содержит действие, которое само указывает на A
, у нас есть утечка памяти.
Итак, вместо B
, содержащего действие, которое A
предоставлено, B
обертывает его в WeakAction
и сохраняет только слабое действие. Когда придет время называть его, B
делает это только в том случае, если WeakAction
все еще жив, и он должен быть до тех пор, пока A
все еще жив.
A
создает это действие внутри метода и не сохраняет ссылку на него самостоятельно - это заданное. Поскольку Action
был создан в контексте конкретного экземпляра A
, этот экземпляр является объектом A
, а когда A
умирает, все слабые ссылки на него становятся null
, поэтому B
знает не называть его и распоряжаться объектом WeakAction
.
Но иногда метод, который генерировал Action
, использует переменные, определенные локально в этой функции. В этом случае контекст, в котором выполняется действие, включает не только экземпляр A
, но также состояние локальных переменных внутри метода (называемого "замыканием" ). Компилятор С# делает это, создавая скрытый вложенный класс для хранения этих переменных (позволяет вызывать его A__closure
), а экземпляр, который становится объектом Action
, является экземпляром A__closure
, а не A
. Это то, о чем пользователь не должен знать. За исключением того, что этот экземпляр A__closure
ссылается только на объект Action
. И так как мы создаем слабую ссылку на цель и не сохраняем ссылку на действие, ссылка на экземпляр A__closure
отсутствует, и сборщик мусора может (и обычно делает это) немедленно избавиться от него. Таким образом, A
живет, A__closure
умирает, и, несмотря на то, что A
все еще ожидает вызова обратного вызова, B
не может этого сделать.
Это ошибка.
Мой вопрос состоял в том, что если кто-то знает, как конструктор WeakAction
, единственный фрагмент кода, который фактически содержит исходный объект Action, временно каким-то магическим способом может извлечь исходный экземпляр A
из A__closure
экземпляр, который он находит в Target
Action
. Если это так, я мог бы продлить жизненный цикл A__closure
в соответствии с A
.