2009-05-17 4 views
1

Я никогда не помню всех правил для реализации интерфейса IDisposable, поэтому я попытался создать базовый класс, который позаботится обо всем этом и упростит реализацию IDisposable. Я просто хотел услышать ваше мнение, если эта реализация будет в порядке, или вы видите то, что я могу улучшить. Предполагается, что пользователь этого базового класса будет извлекать из него, а затем реализовать два абстрактных метода: ReleaseManagedResources() и ReleaseUnmanagedResources(). Так вот код:Правильно ли это IDisposable?

public abstract class Disposable : IDisposable 
{ 
    private bool _isDisposed; 
    private readonly object _disposeLock = new object(); 

    /// <summary> 
    /// called by users of this class to free managed and unmanaged resources 
    /// </summary> 
    public void Dispose() { 
     DisposeManagedAndUnmanagedResources(); 
    } 

    /// <summary> 
    /// finalizer is called by garbage collector to free unmanaged resources 
    /// </summary> 
    ~Disposable() { //finalizer of derived class will automatically call it's base finalizer 
     DisposeUnmanagedResources(); 
    } 

    private void DisposeManagedAndUnmanagedResources() { 
     lock (_disposeLock) //make thread-safe 
      if (!_isDisposed) { //make sure only called once 
       try { //suppress exceptions 
        ReleaseManagedResources(); 
        ReleaseUnmanagedResources(); 
       } 
       finally { 
        GC.SuppressFinalize(this); //remove from finalization queue since cleaup already done, so it's not necessary the garbage collector to call Finalize() anymore 
        _isDisposed = true; 
       } 
      } 
    } 

    private void DisposeUnmanagedResources() { 
     lock (_disposeLock) //make thread-safe since at least the finalizer runs in a different thread 
      if (!_isDisposed) { //make sure only called once 
       try { //suppress exceptions 
        ReleaseUnmanagedResources(); 
       } 
       finally { 
        _isDisposed = true; 
       } 
      } 
    } 

    protected abstract void ReleaseManagedResources(); 
    protected abstract void ReleaseUnmanagedResources(); 

} 

ответ

2

Вы получаете доступ к управляемому объекту _disposeLock в финализаторе. К тому времени, возможно, уже был собран мусор. Не уверен, каковы последствия этого, поскольку вы используете его только для блокировки.

Поток-безопасность кажется излишним. Я не думаю, что вам нужно беспокоиться о конкуренции между потоком GC и потоком приложения.

В противном случае это выглядит правильно.

+0

Спасибо, Джо, да, я заберу замок в финализаторе. – 2009-05-17 15:25:09

+0

Нет, он не может быть собран, поскольку текущий одноразовый содержит ссылку на него. –

+2

«Нет, он не может быть собран» - это неправильно. Его можно было собрать - из MSDN: «Финализаторы двух объектов не гарантируются для запуска в каком-либо конкретном порядке, даже если один объект ссылается на другой. То есть, если объект А имеет ссылку на объект B, и оба имеют финализаторы , Объект B может быть уже завершен, когда начинается финализатор объекта A. »(см. Http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx) – Joe

2

EDIT Да, я неправильно интерпретировал часть о отделяя УТИЛИЗАЦИИ управляемых и неуправляемых ресурсов (все еще на первой чашки кофе).

Однако замок по-прежнему почти не нужен. Да, во время пассивного распоряжения код будет работать в потоке финализатора, который отличается от потока, из которого он изначально выполнялся. Однако, если объект завершается таким образом, CLR уже определила, что нет существующих ссылок на этот объект и, следовательно, его собирает. Таким образом, нет другого места, которое может вызвать ваше распоряжение в то время, и, следовательно, нет причин для блокировки.

Пара других комментариев стиля.

Зачем делать абстрактные методы? Поступая таким образом, вы вынуждаете производные классы внедрять методы для управления управляемыми и неуправляемыми ресурсами, даже если у них нет каких-либо распоряжаться. Правда, нет смысла извлекать из этого класса, если у вас нет ничего, чтобы распоряжаться. Но я думаю, что довольно распространено только одно или другое, но не то, и другое. Я бы сделал их виртуальными или абстрактными.

Также вы предотвращаете двойное уничтожение, но вы ничего не делаете, чтобы предупредить разработчика о том, что они двойным образом уничтожают объект. Я понимаю, что в документации MSDN говорится, что двойное распоряжение должно быть по существу не-операционным, но в то же время, при каких обстоятельствах это должно быть законным? В общем, это плохая идея получить доступ к объекту после его удаления. Двойное распоряжение требует, чтобы удаляемый объект повторно использовался и, вероятно, был ошибкой (это может произойти, если финализация не будет подавлена ​​в активном распоряжении, но это также плохая идея). Если бы я реализовал это, я бы бросил двойное распоряжение, чтобы предупредить разработчика, что они неправильно использовали объект (который использует его после того, как он уже был удален).

+0

WTF? Вы читали мой код? Мой код делает именно то, что вы описываете.Также мне нужно заблокировать код дипоса, потому что по крайней мере финализатор работает в другом потоке, чем мое приложение! – 2009-05-17 15:00:56

+2

@ Герман, да, я неправильно понял эту часть. Однако WTF немного ненужен. – JaredPar

+0

@ JaredPar, извините за WTF, но у меня было ощущение, что вы вообще не читали мой код. Спасибо, что указали, что в финализаторе нет необходимости в блокировке. Я все равно сохраню его в методе Dispose, потому что это не повредит, когда он не используется, и он хорош в тех редких случаях, когда Dispose фактически вызывается пользователем в разных потоках (что возможно в конце концов). Что касается двойного размещения, MSDN говорит: «Метод Dispose должен быть вызван несколько раз, не выбрасывая исключение». – 2009-05-17 15:23:16

12

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

+0

Хорошая точка. Вывод из одноразового использования не позволяет выводить из какого-либо другого класса и который делает одноразовое использование бесполезным во многих случаях. – Vadim

+0

Ну, конечно, но тогда я бы просто скопировал код из этого базового класса. И в тех случаях я владею кодом, я могу просто извлечь из него. – 2009-05-17 15:16:52

+0

@ Vadim: по крайней мере, по vb.net, может быть полезно иметь код, который имеет доступ к параметрам конструктора, запускаемым перед инициализаторами поля. Единственный способ сделать это состоит в том, чтобы конструктор производного класса вызывал конструктор базового класса с этими параметрами и чтобы конструктор базового класса копировал эти параметры в поля производного класса. Если кто-то это делает, можно также использовать логику удаления. – supercat

0

Если вы обеспокоены по поводу безопасности потоков, то я бы использовать легкие сблокированные операции, а не в супертяжелой блокировке:

class MyClass: IDispose { 
    int disposed = 0; 

    public void Dispose() 
    { 
    Dispose(true); 
    GC.SuppressFinalize(this); 
    } 

    public virtual void Dispose(bool disposing) 
    { 
    if (0 == Thread.InterlockedCompareExchange(ref disposed, 1, 0)) 
    { 
     if (disposing) 
     { 
      // release managed resources here 
     } 
     // release unmanaged resources here 
    } 
    } 

    ~MyClass() 
    { 
    Dispose(false); 
    } 
} 
+0

С другой стороны, поток, который терпит неудачу в compareexchange, по-прежнему должен ждать того, который преуспел, иначе он может удалить память из-под подвешенной ветви if, поэтому ей все равно потребуется синхронизация (например, ManualResetEvent), что делает ее в значительной степени столь же тяжелой как замок. –

Смежные вопросы