Почему использовать Dispose как обычный метод плохо?

Пытаюсь это выяснить. Кто-то сказал мне

Утилизация - это не просто метод - это эквивалент деструктора в других языках.

Хорошо. Msdn также очень громко об этом.

Но потом

class Test : IDisposable
{
    public string Property { get; set; } = "Test";
    public void Dispose() => Console.WriteLine("Disposed, very scary");
}

class Program
{
    static void Main(string[] args)
    {
        var test = new Test();
        test.Dispose();
        test.Property = "123";  // but it's disposed OMG! do not do this!
        test.Dispose();

        using (var another = new Test())
            for (int i = 0; i < 10; i++)
            {
                another.Dispose();
                GC.Collect(); // or what should I call to make it crash?
            }

        Console.ReadKey();
    }
}

И проблем нет.

О чем я думаю Dispose:

  • это обычный публичный метод;
  • IDisposable полезно в сочетании с using автоматически звонить Dispose, ничего более;
  • вполне нормально помещать любой код, вызывая его внутри, в любое время, если состояние объекта должным образом поддерживается.

Поправь меня, если я ошибаюсь.

PS: Downvote означает "вопрос плохой / бесполезный / имеет проблему". Если вы просто не согласны с моими мыслями - оставьте комментарий или ответьте. Это будет полезно для людей, которые думают так же, как я сейчас (потому что я не прав? Тогда докажите это)

4 ответа

Решение

Dispose это просто метод, вы можете вызвать его, как и любой другой метод. Это всегда выставлено через IDisposable интерфейс (да, очевидно, вы можете назвать метод Dispose без реализации IDisposableне делай этого!).

Тем не менее, вызов его вручную иногда является запахом кода, запахом кода, который, вероятно, следует использовать using вместо. Под "вручную" здесь я имею в виду Dispose за пределами реализации другого Dispose,

Повторный вызов должен также быть безопасным и задокументирован:

Если метод Dispose объекта вызывается более одного раза, объект должен игнорировать все вызовы после первого. Объект не должен вызывать исключение, если его метод Dispose вызывается несколько раз. Методы экземпляра, отличные от Dispose, могут вызвать исключение ObjectDisposedException, когда ресурсы уже удалены.

(мой акцент)

Если вы звоните Dispose дважды? Нет!, Это также запах кода, который больше не контролирует то, что он сделал и что он оставил делать, и в конечном итоге делает это "просто чтобы быть уверенным". Не делай этого тоже!

Так что, если вы пишете код правильно, вы можете позвонить Dispose вручную.

Вы правы, Dispose это просто еще один метод, принадлежащий IDisposable. У него просто есть дополнительное преимущество - возможность автоматически вызываться по окончании области применения using() - это не эквивалентно деструктору и тому, кто сказал вам, что он не совсем понимает, о чем они говорят.

Я всегда буду использовать блок using(), где это возможно, но если вы будете осторожны, вы можете вместо этого вручную вызвать Dispose, и он будет иметь тот же эффект.

Visual Studio 2015 предлагает (наконец) предложенный шаблон для реализации IDisposable который соответствует руководящим принципам.

Это заглушка, которую VS создает для вас при выборе реализации одноразового шаблона. TODO направляют нас через реализацию.

class Disposable : IDisposable
{
    #region IDisposable Support
    private bool disposedValue = false; // To detect redundant calls

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects).
            }

            // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
            // TODO: set large fields to null.

            disposedValue = true;
        }
    }

    // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
    // ~Disposable() {
    //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
    //   Dispose(false);
    // }

    // This code added to correctly implement the disposable pattern.
    public void Dispose()
    {
        // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        Dispose(true);
        // TODO: uncomment the following line if the finalizer is overridden above.
        // GC.SuppressFinalize(this);
    }
    #endregion
}

Обратите внимание на использование флага, чтобы указать, Dispose уже был вызван. Этот флаг можно использовать, чтобы бросить ObjectDisposedException экземпляры вызовов методов и свойств в рамках реализации классов.

Намерение Dispose всегда был для очистки неуправляемых ресурсов. Тем не менее, синтаксический сахар using ключевое слово (требуется IDisposable) позволяет создавать действительно аккуратные классовые шаблоны для управления доступом к ресурсам, даже если не используются неуправляемые ресурсы.

Как обычно, четко документируйте использование и намерения, и вы не ошибетесь, но избегайте методов под названием "Утилизация", если вы не реализуете интерфейс.

Существует критическая разница между Dispose в.NET по сравнению с деструкторами в других языках: если у кого-то есть указатель на объект на другом языке и объект удален, у него будет недопустимый указатель, и с ним ничего не поделать, в том числе определить, действителен ли он до сих пор, не вызывая неопределенного поведения. В отличие от этого, в.NET, если есть ссылка на объект, который получает Disposed, ссылка будет оставаться действительной ссылкой на рассматриваемый объект. Скорее всего, объект откажется многое сделать, потому что он был уничтожен, но такой отказ утвердительно создается самим объектом. Не определено неопределенное поведение.

С другой стороны, когда деструктор вызывается в других языках, таких как C++, объект может быть уверен, что на него больше нет действительных указателей, но это не так Dispose, Следовательно, код должен избегать объединения открытых объектов и вместо этого иметь каждый запрос на новый объект, возвращающий новый объект (который может быть оболочкой для объекта пула). Если за пределами оболочки нет ссылки на объединенный объект, и Dispose делает эту ссылку недействительной перед возвратом объекта в пул, тогда запрос будущего объекта может быть удовлетворен путем инкапсуляции объекта пула в новую оболочку, которая снова будет содержать единственную ссылку на него.

Другие вопросы по тегам