#c# #asp.net-core #dependency-injection #asp.net-core-signalr
#c# #сборка мусора #замыкания
Вопрос:
Нужно ли мне установить для MyAction значение null, чтобы сборка мусора могла продолжаться с любым из этих классов?
Меня меньше беспокоит, когда оба класса должны иметь почти одинаковый срок службы. Мой вопрос более уместен, когда срок службы Class1 намного больше, чем Class2, или когда срок службы Class2 намного больше, чем Class1.
Код здесь сокращен. Предположим, что и Class1, и Class2 содержат другие члены и методы, которые могут повлиять на их срок службы.
public class Class1 : IDisposable
{
public Action<string> MyAction { get; set; }
// Is this necessary?
public void Dispose()
{
MyAction = null;
}
}
public class Class2
{
string _result = string.Empty;
public void DoSomething()
{
Class1 myClass1 = new Class1();
myClass1.MyAction = s => _result = s;
myClass1.Dispose();
}
}
Ответ №1:
Нужно ли мне установить для MyAction значение null, чтобы сборка мусора могла продолжаться с любым из этих классов?
Нет. Каждый раз, когда вы «утилизируете» управляемый ресурс, велика вероятность, что вы делаете это неправильно. Пусть сборщик мусора выполняет свою работу.
Меня меньше беспокоит, когда оба класса должны иметь почти одинаковый срок службы. Мой вопрос более уместен, когда срок службы Class1 намного больше, чем Class2, или когда срок службы Class2 намного больше, чем Class1.
Вопрос не имеет никакого смысла; у классов нет времени жизни. Хранилища имеют время жизни. О каких именно хранилищах вы беспокоитесь? Можете ли вы прояснить вопрос?
Я отмечаю, что существуют очень серьезные опасения по поводу продления срока службы закрытых переменных с помощью замыканий; однако ваш вопрос настолько расплывчатый, что трудно понять, сталкиваетесь ли вы с такой ситуацией. Позвольте мне продемонстрировать:
class Expensive
{
public byte[] huge = MakeHugeByteArray();
}
class Cheap
{
public int tiny;
}
class C
{
public static Func<Cheap> longLived;
public static void M()
{
Expensive expensiveLocal = new Expensive();
Cheap cheapLocal = new Cheap();
Func<Expensive> shortLived = ()=>expensiveLocal ;
C.longLived = ()=>cheapLocal;
}
}
Каково время жизни локальной переменной expensiveLocal ?Время жизни локальной переменной обычно короткое; обычно локальные переменные живут не дольше, чем активация метода. Однако нахождение в замыкании увеличивает время жизни локальной переменной произвольно долго, до времени жизни замыкания. В данном конкретном случае оба лямбда-выражения разделяют замыкание, а это означает, что время жизни локальной переменной expensiveLocal по крайней мере столько же, сколько время жизни локальной переменной cheapLocal , которая имеетбесконечно долгое время жизни, потому что ссылка на закрытие только что была сохранена в статическом поле, которое живет вечно. Этот большой массив байтов может никогда не быть восстановлен, даже если единственное, что, кажется, ссылается на него, было собрано давно; закрытие — это скрытая ссылка.
Многие языки имеют эту проблему; C #, VB, JScript и так далее имеют лексические замыкания, которые не разделены на группы переменных по времени жизни. Мы рассматриваем возможность изменения C # и VB, чтобы улучшить управление жизненным циклом для замыканий, но на данный момент это работа далекого будущего, поэтому никаких гарантий.
Комментарии:
1. Было бы чрезвычайно полезно, если бы вы могли немного рассказать о том, что значит «совместно использовать закрытие». Визуально кажется, что переменные expensiveLocal и cheapLocal совершенно не связаны. expensiveLocal, похоже, живет только до тех пор, пока shortLived , что является областью действия метода M. Каковы правила, когда такие несвязанные переменные становятся частью одного и того же замыкания?
2. @Konstantin: Многие люди, похоже, считают, что определяющей характеристикой локальной переменной является то, что у нее короткое время жизни . Хотя верно, что многие локальные переменные имеют короткое время жизни, и это правда, что это выгодно , короткое время жизни не является определяющей характеристикой. То, что делает локальную переменную «локальной», — это не ее время жизни, а скорее ее область действия. Помните, что область действия определяется как область кода, в которой объект может быть указан по имени . Локальное значение является локальным, потому что на него ссылаются только по имени локально в теле метода (или лямбда).
3. @Konstantin: Время жизни локального объекта обычно заканчивается, когда элемент управления покидает область; однако время жизни закрытых локальных объектов увеличивается. Спецификация не дает никаких правил для того, насколько коротким должно быть это расширение, только для того, как долго оно должно быть: оно должно быть продлено как минимум до срока службы делегата. Срок службы может быть продлен намного дольше, и в этом неудачном случае на самом деле так и есть. Нет никаких правил , когда два делегата совместно используют закрытие; это детали реализации компилятора и могут быть изменены в любое время.
4. Хорошо, теперь я попытался скомпилировать ваш код и вижу, что C # действительно генерирует один тип для обоих замыканий, и существует только один экземпляр этого типа, созданный в «M». Как дешевые, так и дорогие локальные объекты становятся полями этого типа / экземпляра, и огромный массив становится бессмертным! Это так неожиданно. Теперь я понимаю ваше теоретическое объяснение гарантий / правил компилятора совершенно по-другому. Могу ли я смиренно предложить это в качестве темы поста для вашего замечательного блога? Я думаю, что многие люди были бы очень удивлены 🙂
5. @Konstantin: Отличная идея. Я написал эту статью в 2007 году: blogs.msdn.com/b/ericlippert/archive/2007/06/06 /…
Ответ №2:
Нет. Нет необходимости устанавливать ссылку на null.
Dispose()
предназначен для очистки неуправляемых ресурсов. Action<string>
является управляемым ресурсом и будет должным образом обработан CLR, когда экземпляр Class1
выпадет из области видимости в конце DoSomething()
.
Комментарии:
1. Вопрос — Обработчики событий являются управляемыми ресурсами, но если они неправильно отключены, они, похоже, препятствуют сбору мусора вашими объектами…
2. @PaulMatovich — Да, но это только в определенных ситуациях (один класс будет содержать ссылку на метод другого, что означает, что вещи могут не выпадать из области видимости, как ожидалось). Здесь это не так … вы просто устанавливаете действие<string> для анонимной функции.
3. Спасибо, это полезно. Моя проблема, которую я вижу, заключается в том, что class1 содержит ссылку на код, предоставляемый class2, и переменную уровня класса class2 и, следовательно, не является кандидатом на сборку мусора. Но я понимаю, что закрытие обеспечивает некоторое действие дыма и зеркала для отключения этой ссылки.
Ответ №3:
Нет, вы этого не делаете. Сборщик мусора обработает это.
Ответ №4:
Как только doSomething возвращается, MyClass1 является кандидатом на сборку мусора. Когда класс собирает мусор, все его члены также собираются, если нет выдающихся ссылок. Поэтому вам не нужно делать то, что вы делаете.