这是否是类层次结构的“传统”处置模式的合法替代方案?

时间:2009-09-09 01:52:29

标签: c# dispose idisposable finalizer boilerplate

我不喜欢样板代码:复制粘贴重用可能容易出错。即使您使用代码段或智能模板,也无法保证其他开发人员所做的,这意味着无法保证他们做得对。而且,如果你必须看到代码,你必须理解和/或维护它。

我想从社区中了解到:我对类层次结构的 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 的子类。基本上,我把这个问题全部考虑在内。

我可能错过了一些边缘情况和深奥的情况,所以我邀请任何人在这种方法中挖洞或者通过更正来支撑它。

其他替代方案可能是使用单个队列&lt; Disposer&gt;处理器 DisposableObject 而不是两个列表中;在这种情况下,当调用处理器时,它们将从列表中删除。我可以想到其他一些细微的变化,但它们具有相同的一般结果:没有样板代码。

3 个答案:

答案 0 :(得分:5)

您可能遇到的第一个问题是C#只允许您从单个基类继承,在这种情况下,始终DisposableObject。在这里,您通过强制其他层来混乱您的类层次结构,以便需要从DisposableObject和其他对象继承的类可以这样做。

通过此实施,您还会引入大量的开销和维护问题(更不用说每当有新的项目出现时重复的培训成本,您必须解释他们应该如何使用此实现而不是定义的模式)。你知道有两个状态可以跟踪你的两个列表,没有对动作调用的错误处理,调用动作时的语法看起来很“奇怪”(虽然调用数组中的方法可能很常见,在数组访问后简单地放置()的语法看起来很奇怪。)

我理解减少你必须编写的样板数量的愿望,但可处置性通常不是我建议采用捷径或偏离模式的那些领域之一。我通常得到的最接近的是使用辅助方法(或扩展方法)将实际调用包装到给定对象上的Dispose()。这些调用通常如下所示:

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

这可以使用辅助方法简化,但请记住,FxCop(或任何其他检查正确的dispose实现的静态分析工具)会抱怨。

就性能而言,请记住,您正在使用此类实现进行大量委托调用。就委托人而言,这比普通的方法调用要贵一些。

可维护性肯定是一个问题。正如我所提到的,每当有新的项目进入项目时,您就会有重复的培训成本,并且您必须解释他们应该如何使用此实现而不是定义的模式。不仅如此,每个人都记得将一次性物品添加到列表中时遇到问题。

总体而言,我认为这样做是一个糟糕的主意,会导致很多问题,特别是随着项目和团队规模的增加。

答案 1 :(得分:2)

我偶尔需要一次跟踪几个打开的文件或其他资源。当我这样做时,我使用类似于以下的实用程序类。然后,对象仍然按照您的建议实现Displose(),即使跟踪多个列表(托管/非托管)对于开发人员来说也很容易和明显。此外,从List对象派生并非偶然,它允许您在需要时调用Remove(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;
        }
    }

答案 2 :(得分:0)

我喜欢csharptest答案中的一般模式。设置一个基类处理的基类有点限制,但是如果你使用vb.net或者不介意一些带有线程静态变量的游戏,那么一个特别设计的基类就可以注册变量以便处理当它们在字段初始化器或派生类构造函数中创建时(通常,如果在字段初始化程序中抛出异常,则无法处置任何已分配的IDisposable字段,并且如果派生类的构造函数抛出异常,则部分创建的基础对象无法自行配置。)

但是,我不打扰您的非托管资源列表。具有终结器的类不应该包含对最终化不需要的任何对象的引用。相反,最终化所需的东西应该放在它自己的类中,而“main”类应该创建后一个类的实例并保持对它的引用。