Я еще не видел упомянутое конкретное использование Dispose
, поэтому я решил указать общий источник утечек памяти, если не использовать шаблон dispose.
Visual Studio 2017 на самом деле жалуетсяоб этом через статический анализ кода, который я должен "реализовать шаблон утилизации".Обратите внимание, что я использую SonarQube и SolarLint, и я не верю, что Visual Studio поймает это в одиночку.FxCop (другой инструмент статического анализа кода), вероятно, будет, хотя я не проверял это.
Я отмечаю приведенный ниже код для демонстрации шаблона dispose, который также предназначен для защиты от чего-то подобного, у которого нет неуправляемых ресурсов.:
public class Foo : IDisposable
{
IDisposable boo;
public void Dispose()
{
boo?.Dispose();
}
}
public class Bar : Foo
{
//Memory leak possible here
public event EventHandler SomeEvent;
//Also bad code, but will compile
public void Dispose()
{
someEvent = null;
//Still bad code even with this line
base.Dispose();
}
}
Выше показан очень плохой код.Не делай этого.Почему этот ужасный код?Вот из-за этого:
Foo foo = new Bar();
//Does NOT call Bar.Dispose()
foo.Dispose();
Предположим, этот ужасающий код был выставлен в нашем публичном API.Рассмотрим перечисленные выше классы, используемые потребителями:
public sealed class UsesFoo : IDisposable
{
public Foo MyFoo { get; }
public UsesFoo(Foo foo)
{
MyFoo = foo;
}
public void Dispose()
{
MyFoo?.Dispose();
}
}
public static class UsesFooFactory
{
public static UsesFoo Create()
{
var bar = new Bar();
bar.SomeEvent += Bar_SomeEvent;
return new UsesFoo(bar);
}
private static void Bar_SomeEvent(object sender, EventArgs e)
{
//Do stuff
}
}
Являются ли потребители идеальными?Нет .... UsesFooFactory
, вероятно, следует также отказаться от подписки на событие.Но он выделяет общий сценарий, когда событие подписчик переживает издатель .
Я видел, как события вызывают бесчисленные утечки памяти.Особенно в очень больших или чрезвычайно высокопроизводительных кодовых базах.
Я также с трудом могу сосчитать, сколько раз я видел, как объекты жили долго после их использования.Это очень распространенный способ, которым многие профилировщики обнаруживают утечки памяти (удаленные объекты, все еще удерживаемые каким-либо корнем GC).
Опять чрезмерно упрощенный пример и ужасный код.Но на самом деле никогда не рекомендуется вызывать Dispose
для объекта, а , а не ожидать, что он удалит весь объект, независимо от того, был он получен миллион раз или нет.
Edit
Обратите внимание, что этот ответ преднамеренно касается только управляемых ресурсов, демонстрируя, что шаблон dispose также полезен в этом сценарии.Это целенаправленно не относится к варианту использования для неуправляемых ресурсов, так как я чувствовал, что недостаточно внимания уделяется только управляемым видам использования.И здесь есть много других хороших ответов, которые говорят об этом.
Я, однако, отмечу несколько быстрых вещей, которые важны, когда речь идет о неуправляемых ресурсах.Приведенный выше код может не адресовать неуправляемые ресурсы, но я хочу пояснить, что он не противоречит тому, как они должны обрабатываться.
Чрезвычайно важно использовать финализаторы , когдаВаш класс отвечает за неуправляемые ресурсы.Вкратце, финализаторы автоматически вызываются сборщиком мусора.Таким образом, это дает вам разумную гарантию, что его всегда вызывают в определенный момент времени.Он не пуленепробиваемый, но далек от надежды на то, что пользовательский код вызывает Dispose
.
Эта гарантия не верна Dispose
.GC может вернуть объект без вызова Dispose
.Это основная причина, по которой финализаторы используются для неуправляемых ресурсов.Сам GC обрабатывает только управляемые ресурсы.
Но я также отмечу, что столь же важно, чтобы финализаторы не использовались для очистки управляемых ресурсов.Существует бесчисленное множество причин, по которым (в конце концов, это задача GC - сделать это), но одним из самых больших недостатков использования финализаторов является задержка сбора мусора на объекте.
GC, видя, что объект свободен для восстановления, ноимеет финализатор, задержит сбор, поместив объект в очередь финализатора.Это добавляет значительное ненужное время жизни объекту, а также усиливает давление на GC.
Наконец, я отмечу, что финализаторы по этой причине недетерминированы, несмотря на то, что синтаксис похож на деструктор в C ++.Они очень разные звери.Вы никогда не должны полагаться на финализатор для очистки неуправляемых ресурсов в определенный момент времени.