В одном из проектов, в которых я участвую, существует обширное использование 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.