Почему рекомендуемый шаблон удаления добавляет удаляемое поле на каждом уровне иерархии? - PullRequest
2 голосов
/ 29 мая 2020

Паттерн Dispose, как известно, сложно понять, особенно когда у нас есть иерархия классов, которая должна располагать вещи на разных уровнях. Ниже представлена ​​рекомендуемая реализация, взятая из Реализация метода Dispose - Microsoft Docs .

using System;

class BaseClass : IDisposable
{
    // To detect redundant calls
    private bool _disposed = false;

    ~BaseClass() => Dispose(false);

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }

        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.

        _disposed = true;
    }
}

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

class DerivedClass : BaseClass
{
    // To detect redundant calls
    private bool _disposed = false;

    // Instantiate a SafeHandle instance.
    private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }

        if (disposing)
        {
           // Dispose managed state (managed objects).
            _safeHandle?.Dispose();
        }

        _disposed = true;

        // Call base class implementation.
        base.Dispose(disposing);
    }
}

В этой реализации я не получаю того преимущества, которое мы имеем при добавлении _disposed поле на каждом уровне иерархии? Вместо этого мы могли бы позаботиться о _disposed только на верхнем уровне иерархии (тот, который непосредственно реализует IDisposable и не заботиться об этом в производных классах.

Примерно так:

using System;

class BaseClass : IDisposable
{
    // To detect redundant calls
    private bool _disposed = false;

    ~BaseClass()
    {
       if (_disposed)
       {
          return;
       }

       Dispose(false);
       _disposed = true;
    }

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        Dispose(true);
        GC.SuppressFinalize(this);
        _disposed = true;
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        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.
    }
}

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

class DerivedClass : BaseClass
{
    // Instantiate a SafeHandle instance.
    private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
           // Dispose managed state (managed objects).
            _safeHandle?.Dispose();
        }

        // Call base class implementation.
        base.Dispose(disposing);
    }
}

Это настолько широко используемый пример кода, что, безусловно, должна быть веская причина для его реализации с повторением _disposed на каждом уровне, но я просто не могу найти ни одного.

Это немного больше кода в базовом классе, но меньше беспокойства в производном классе, и некоторая повторяющаяся информация удалена.

Что еще мне не хватает?

Изменить:

Поскольку @InBetween правильно говорит, что одним из недостатков моей реализации является то, что если вам нужно будет проверить, размещен ли ваш объект в одном методе производного класса, вы не сможете это проверить. Давайте исправим это проблема, сделав его защищенным свойством с частным набором.

using System;

class BaseClass : IDisposable
{
    // To detect redundant calls
    protected bool Disposed { get; private set; } = false;

    ~BaseClass()
    {
       if (Disposed)
       {
          return;
       }

       Dispose(false);
       Disposed = true;
    }

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        if (Disposed)
        {
            return;
        }

        Dispose(true);
        GC.SuppressFinalize(this);
        Disposed = true;
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        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.
    }
}

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

class DerivedClass : BaseClass
{
    // Instantiate a SafeHandle instance.
    private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    public void DoSomething()
    {
       if(Disposed)
       {
           throw new ObjectDisposedException("Cannot access disposed resource");
       }
    }    

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
           // Dispose managed state (managed objects).
            _safeHandle?.Dispose();
        }

        // Call base class implementation.
        base.Dispose(disposing);
    }
}

Ответы [ 2 ]

4 голосов
/ 29 мая 2020

Если вы наследуете от одноразового класса, одно из двух условий должно быть истинным.

  1. Ваш subcless не вводит новый одноразовый ресурс. В этом случае вам не нужно переопределять Dispose, и вопрос спорный.

  2. Ваш подкласс представляет новый одноразовый ресурс. В этом случае вы собираетесь переопределить Dispose, вставить свой собственный код утилизации, а затем позвонить base.Dispose. Флаг _disposed нужен, чтобы помочь вам не забыть предотвратить повторное выполнение кода утилизации.

Вы, конечно, можете удалить _disposed, если хотите. Но вы, вероятно, не особо заботитесь о флаге базового класса '_disposed, если он вообще есть. Его беспокоит то, что он выбрасывает, а вы беспокоитесь о своем.

4 голосов
/ 29 мая 2020

Причина проста. В вашей реализации вы не можете использовать _disposed в производном типе, чтобы проверить, вызывается ли какой-либо метод, когда объект уже удален, и предпринять необходимые действия. В вашей реализации вам нужно будет создать свой собственный избыточный флаг isDisposed, который противоречит цели; у вас уже есть один "бесплатно" из самого выкройки в соответствии с инструкциями.

Можно было бы сделать случай, чтобы сделать _disposed protected.

...