2009-09-09 4 views
2

Я не являюсь поклонником шаблона кода: повторное использование копий-пасты потенциально подвержено ошибкам. Даже если вы используете фрагменты кода или смарт-шаблоны, нет гарантии, что другой разработчик сделал это, и это означает, что нет никакой гарантии, что они сделали это правильно. И, если вам нужно увидеть код, вы должны понять его и/или сохранить его.Является ли это законной альтернативой «традиционному» шаблону размещения для иерархии классов?

Что я хочу знать из сообщества: является ли моя реализация IDispose для иерархии классов законной альтернативой "traditional" dispose pattern? По законным, я имею в виду правильные, разумно хорошо исполняемые, надежные и поддерживаемые.

Я согласен с тем, что эта альтернатива выглядит неправильно, но если да, то я бы хотел знать, почему.

Эта реализация предполагает, что вы имеете полный контроль над иерархией классов; если вы этого не сделаете, вам, вероятно, придется вернуться к шаблону. Звонки на Добавить *() обычно будет выполнен в конструкторе.

public abstract class DisposableObject : IDisposable 
{ 
    protected DisposableObject() 
    {} 

    protected DisposableObject(Action managedDisposer) 
    { 
    AddDisposers(managedDisposer, null); 
    } 

    protected DisposableObject(Action managedDisposer, Action unmanagedDisposer) 
    { 
    AddDisposers(managedDisposer, unmanagedDisposer); 
    } 

    public bool IsDisposed 
    { 
    get { return disposeIndex == -1; } 
    } 

    public void CheckDisposed() 
    { 
    if (IsDisposed) 
     throw new ObjectDisposedException("This instance is disposed."); 
    } 

    protected void AddDisposers(Action managedDisposer, Action unmanagedDisposer) 
    { 
    managedDisposers.Add(managedDisposer); 
    unmanagedDisposers.Add(unmanagedDisposer); 
    disposeIndex++; 
    } 

    protected void AddManagedDisposer(Action managedDisposer) 
    { 
    AddDisposers(managedDisposer, null); 
    } 

    protected void AddUnmanagedDisposer(Action unmanagedDisposer) 
    { 
    AddDisposers(null, unmanagedDisposer); 
    } 

    public void Dispose() 
    { 
    if (disposeIndex != -1) 
    { 
     Dispose(true); 
     GC.SuppressFinalize(this); 
    } 
    } 

    ~DisposableObject() 
    { 
    if (disposeIndex != -1) 
     Dispose(false); 
    } 

    private void Dispose(bool disposing) 
    { 
    for (; disposeIndex != -1; --disposeIndex) 
    { 
     if (disposing) 
      if (managedDisposers[disposeIndex] != null) 
       managedDisposers[disposeIndex](); 
     if (unmanagedDisposers[disposeIndex] != null) 
      unmanagedDisposers[disposeIndex](); 
    } 
    } 

    private readonly IList<Action> managedDisposers = new List<Action>(); 
    private readonly IList<Action> unmanagedDisposers = new List<Action>(); 
    private int disposeIndex = -1; 
} 

Это «полное» осуществление в том смысле, я обеспечиваю поддержку завершения (зная, что большинство реализаций не требуется финализации), проверяя, расположен ли объект и т.д. Реальная реализация может удалить финализации , например, или создать подкласс DisposableObject, который включает финализатор. В принципе, я бросил все, что мог думать только по этому вопросу.

Возможно, есть некоторые краевые случаи и эзотерические ситуации, которые я пропустил, поэтому я приглашаю кого-либо выкалывать дыры в этом подходе или беречь его с исправлениями.

Другие альтернативы может быть использование одной очереди <Disposer> в отвечающих за удаление DisposableObject вместо двух списков; в этом случае, когда вызывающие вызовы вызываются, они удаляются из списка. Есть и другие незначительные вариации, о которых я могу думать, но они имеют одинаковый общий результат: никакого шаблона кода.

+1

Это все еще страдает от того факта, что разработчики должны помнить о вызове метода Add() для агрегированных типов. Лично я не думаю, что это может принести пользу по обычной схеме. –

+5

Не делайте этого. Используйте Pattern Dispose как написано. Опытные разработчики сразу поймут код, вы покроете все базы, такие инструменты, как FxCop, помогут проверить его, вы можете написать фрагменты, чтобы точно реализовать его, и вы будете придерживаться стандартов Microsoft. http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae – TrueWill

+0

Как в стороне, если вы хотите, это DisposableCollection , вы должны быть в состоянии найти количество реализаций этого. – TrueWill

ответ

5

Первая проблема, с которой вы столкнетесь, - это то, что C# позволяет вам наследовать только из одного базового класса, который в этом случае будет всегда be DisposableObject. Прямо здесь вы загромождали свою иерархию классов, заставляя дополнительные слои, чтобы классы, которые должны наследовать от DisposableObject и некоторых других объектов, могут это сделать.

Вы также представляете много проблем с накладными расходами и обслуживанием в будущем с этой реализацией (не говоря уже о повторных затратах на обучение каждый раз, когда кто-то новый приходит к проекту, и вы должны объяснить, как они должны использовать эту реализацию, а не определенный шаблон). Вы знаете, что несколько состояний отслеживать с помощью двух ваших списков, нет обработки ошибок вокруг вызовов к действиям, синтаксис при вызове действия выглядит «wierd» (хотя может быть обычным вызывать метод из массива, синтаксис простого ввода() после доступа к массиву выглядит просто странно).

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

if (someObject != null) 
{ 
    someObject.Dispose(); 
} 

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

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

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

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

+0

Хорошие моменты, особенно на тему обучения. Как я уже сказал, я в порядке с неправильным. Мне интересно узнать, что еще может получиться. – Kit

2

Мне иногда нужно отслеживать несколько открытых файлов за раз или на другие ресурсы. Когда я это делаю, я использую класс утилиты, подобный следующему. Затем объект все еще реализует Displose(), как вы предлагаете, даже отслеживание нескольких списков (управляемых/неуправляемых) легко и понятно для разработчиков, что происходит. Кроме того, вывод из объекта List не случайно, он позволяет вам при необходимости удалять (obj). Мой конструктор обычно выглядит следующим образом:

 _resources = new DisposableList<IDisposable>(); 
     _file = _resources.BeginUsing(File.Open(...)); 

А вот класс:

class DisposableList<T> : List<T>, IDisposable 
     where T : IDisposable 
    { 
     public bool IsDisposed = false; 
     public T BeginUsing(T item) { base.Add(item); return item; } 
     public void Dispose() 
     { 
      for (int ix = this.Count - 1; ix >= 0; ix--) 
      { 
       try { this[ix].Dispose(); } 
       catch (Exception e) { Logger.LogError(e); } 
       this.RemoveAt(ix); 
      } 
      IsDisposed = true; 
     } 
    } 
0

Мне нравится общий шаблон от ответа csharptest в. Наличие базового класса, предназначенного для удаления, немного ограничено, но если вы используете vb.net или не возражаете против игр с потоковыми статическими переменными, специально разработанный базовый класс позволит зарегистрировать переменные для удаления даже когда они создаются в инициализаторах полей или конструкторах производного класса (обычно, если исключение выбрано в инициализаторе поля, нет способа распоряжаться уже выделенными полями IDisposable, и если конструктор производного класса выдает исключение, никак не для того, чтобы частично созданный базовый объект сам распоряжался).

Я бы не стал беспокоиться о вашем списке неуправляемых ресурсов. Классы с финализаторами не должны содержать ссылки на любые объекты, не требуемые для завершения. Вместо этого материал, необходимый для завершения, должен быть помещен в собственный класс, а «основной» класс должен создать экземпляр этого последнего класса и сохранить ссылку на него.

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

  • Нет связанных вопросов^_^