2

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

我想从社区知道的是:我对类层次结构的IDispose实现是“传统”处置模式的合法替代方案吗?合法的,我的意思是正确的、性能相当好的、健壮的和可维护的。

我可以接受这种选择是完全错误的,但如果是,我想知道为什么。

此实现假定您可以完全控制类层次结构;如果你不这样做,你可能不得不求助于样板代码。对Add*()的调用通常在构造函数中进行。

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的子类。基本上,我只为这个问题投入了我能想到的一切。

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

其他选择可能是在DisposableObject中使用单个Queue<Disposer> 处理器而不是两个列表;在这种情况下,当调用处理程序时,它们会从列表中删除。我能想到其他一些细微的变化,但它们具有相同的一般结果:没有样板代码。

4

3 回答 3

5

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

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

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

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

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

就性能而言,请记住,您正在使用这种类型的实现进行大量委托调用。就委托的性质而言,这比普通方法调用的成本要高一些。

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

总的来说,我认为这样做是一个坏主意,会导致很多问题,尤其是随着项目和团队规模的增加。

于 2009-09-09T02:11:36.317 回答
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;
        }
    }
于 2009-09-09T02:21:40.397 回答
0

我喜欢 csharptest 回答的一般模式。围绕处置设计一个基类有点限制,但是如果您使用的是 vb.net 或者不介意一些带有线程静态变量的游戏,那么一个专门设计的基类甚至可以注册用于处置的变量当它们在字段初始值设定项或派生类构造函数中创建时(通常,如果在字段初始值设定项中引发异常,则无法处置任何已分配的 IDisposable 字段,并且如果派生类的构造函数引发异常,则部分创建的基础对象无法自行处理)。

I wouldn't bother with your unmanaged resources list, though. Classes with finalizers shouldn't hold references to any objects not required for finalization. Instead, the stuff needed for finalization should be placed in its own class, and the "main" class should create an instance of that latter class and keep a reference to it.

于 2011-01-04T20:34:28.977 回答