【问题标题】:Is this a legitimate alternative to the "traditional" dispose pattern for class hierarchies?这是类层次结构的“传统”处置模式的合法替代方案吗?
【发布时间】:2009-09-09 01:52:28
【问题描述】:

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

我想从社区中了解的是:我为类层次结构实现的 IDispose"traditional" dispose pattern 的合法替代方案吗?我所说的合法​​是指正确、性能相当不错、健壮且可维护。

我可以接受这种替代方案是完全错误的,但如果是,我想知道原因。

此实现假定您可以完全控制类层次结构;如果你不这样做,你可能不得不求助于样板代码。对 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 disposers 而不是两个列表;在这种情况下,当调用处理程序时,它们会从列表中删除。我能想到其他一些细微的变化,但它们具有相同的一般结果:没有样板代码。

【问题讨论】:

  • 这仍然受到这样一个事实的影响,即实现者必须记住为聚合类型调用 Add() 方法。就个人而言,我认为这与通常的模式相比没有任何好处。
  • 别这样。使用所写的 Dispose 模式。有经验的开发人员会一眼看懂代码,你会覆盖所有的基础,FxCop 等工具可以帮助验证它,你可以编写 sn-ps 来准确地实现它,你会遵守微软的标准。 bluebytesoftware.com/blog/…
  • 顺便说一句,如果你想要的是 DisposableCollection,你应该能够找到它的许多实现。
  • 好的,我刚刚意识到至少存在一个问题。如果 unmanagedDisposer 抛出,则不会运行任何“基本”处理程序。这在传统模式中不会发生,因为基本终结器将始终运行。
  • 这个问题几乎完全一样:stackoverflow.com/questions/874764/…

标签: c# dispose idisposable finalizer boilerplate


【解决方案1】:

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

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

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

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

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

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

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

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

【讨论】:

  • 好点,尤其是在培训问题上。正如我所说,我可以接受这是错误的。我很想知道还会发生什么。
【解决方案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;
        }
    }

【讨论】:

    【解决方案3】:

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

    不过,我不会打扰您的非托管资源列表。具有终结器的类不应包含对终结器不需要的任何对象的引用。相反,终结所需的东西应该放在它自己的类中,“主”类应该创建后者的实例并保留对它的引用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-06
      • 1970-01-01
      相关资源
      最近更新 更多