这是“传统”技术的合法替代方案吗?处理类层次结构的模式?
我不喜欢样板代码:复制粘贴重用可能容易出错。即使您使用代码片段或智能模板,也不能保证其他开发人员会这样做,这意味着不能保证他们做得正确。而且,如果您必须查看代码,则必须理解它和/或维护它。
我想从社区知道的是:我对类层次结构的 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 子类。基本上,为了这个问题,我把我能想到的一切都投入了。
我可能错过了一些边缘情况和深奥的情况,因此我邀请任何人在这种方法中找出漏洞或通过更正来支持它。
其他替代方案可能是使用单个队列
I am not a fan of boilerplate code: copy-paste reuse is potentially error-prone. Even if you use code snippets or smart templates, there is no guarantee the other developer did, which means there's no guarantee they did it right. And, if you have to see the code, you have to understand it and/or maintain it.
What I want to know from the community is: is my implementation of IDispose for a class hierarchy a legitimate alternative to the "traditional" dispose pattern? By legitimate, I mean correct, reasonably well performing, robust, and maintainable.
I am ok with this alternative being plain wrong, but if it is, I'd like to know why.
This implementation assumes you have full control over the class hierarchy; if you don't you'll probably have to resort back to boilerplate code. The calls to Add*() would typically be made in the constructor.
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;
}
This is a "complete" implementation in the sense I provide support for finalization (knowing most implementations don't need a finalizer), checking whether an object is disposed, etc. A real implementation may remove the finalizer, for example, or create a subclass of DisposableObject that includes the finalizer. Basically, I threw in everything I could think of just for this question.
There are probably some edge cases and esoteric situations I've missed, so I invite anyone to poke holes in this approach or to shore it up with corrections.
Other alternatives might be to use a single Queue<Disposer> disposers in DisposableObject instead of two lists; in this case, as disposers are called, they are removed from the list. There are other slight variations I can think of, but they have the same general result: no boilerplate code.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您可能遇到的第一个问题是,C# 只允许您从单个基类继承,在本例中该基类始终为
DisposableObject
。在这里,您通过强制附加层使类层次结构变得混乱,以便需要从DisposableObject
继承的类和其他一些对象可以这样做。通过此实现,您还会引入许多开销和维护问题(更不用说每次新人加入该项目时的重复培训成本,并且您必须解释他们应该如何使用此实现而不是定义的模式) 。您知道需要跟踪两个列表的多个状态,对操作的调用没有错误处理,调用操作时的语法看起来“奇怪”(虽然从数组调用方法可能很常见,简单地将 () 放在数组访问后面的语法看起来很奇怪)。
我理解减少必须编写的样板文件数量的愿望,但可处置性通常不是我建议采取捷径或以其他方式偏离模式的领域之一。我通常得到的最接近的方法是使用辅助方法(或扩展方法)来包装对给定对象上的
Dispose()
的实际调用。这些调用通常看起来像:这可以使用辅助方法来简化,但请记住,FxCop(或检查正确处置实现的任何其他静态分析工具)会抱怨。
就性能而言,请记住,您将使用这种类型的实现进行大量委托调用。从委托的性质来看,这比普通方法调用的成本要高一些。
可维护性在这里肯定是一个问题。正如我所提到的,每次新人加入项目时,您都会产生重复的培训成本,并且您必须解释他们应该如何使用此实现而不是定义的模式。不仅如此,您还会遇到每个人都记得将一次性物品添加到您的清单中的问题。
总的来说,我认为这样做是一个坏主意,会在以后引起很多问题,特别是随着项目和团队规模的增加。
The first issue you will potentially hit is that C# only allows you to inherit from a single base class, which in this case will always be
DisposableObject
. Right here you have cluttered your class hierarchy by forcing additional layers so that classes that need to inherit fromDisposableObject
and some other object can do so.You are also introducing a lot of overhead and maintenance issues down the road with this implementation (not to mention the repeated training costs everytime someone new comes on to the project and you have to explain how they should use this implementation rather than the defined pattern). You know have multiple states to keep track of with your two lists, there is no error handling around the calls to the actions, the syntax when calling an action looks "wierd" (while it may be common to invoke a method from an array, the syntax of simply putting the () after the array access just looks strange).
I understand the desire to reduce the amount of boilerplate you have to write, but disposability is generally not one of those areas that I would recommend taking short-cuts or otherwise deviating from the pattern. The closest I usually get is to use a helper method (or an extension method) that wraps the actual call to
Dispose()
on a given object. These calls typically look like:This can be simplified using a helper method, but keep in mind that FxCop (or any other static analysis tool that checks for correct dispose implementations) will complain.
As far as performance is concerned, keep in mind that you are making a lot of delegate calls with this type of implementation. This is, by nature of a delegate, somewhat more costly than a normal method call.
Maintainability is definately an issue here. As I mentioned, you have the repeated training costs everytime someone new comes on to the project and you have to explain how they should use this implementation rather than the defined pattern. Not only that, you have problems with everyone remembering to add their disposable objects to your lists.
Overall, I think doing this is a bad idea that will cause a lot of problems down the road, especially as the project and team size increase.
我偶尔需要一次跟踪多个打开的文件或其他资源。当我这样做时,我使用类似于以下的实用程序类。然后该对象仍然按照您的建议实现 Displose(),即使跟踪多个列表(托管/非托管)对于开发人员来说也很容易且显而易见。此外,从 List 对象派生并非偶然,它允许您在需要时调用 Remove(obj)。我的构造函数通常如下所示:
这是类:
I occasionally have need for tracking several open files at a time or other resources. When I do I use a utility class similar to the following. Then the object still implements the Displose() as you suggest, even tracking multiple lists (managed/unmanaged) is easy and obvious to developers whats going on. Additionally the derivation from the List object is no accident, it allows you call Remove(obj) if needed. My constructor usually looks like:
And here is the class:
我喜欢 csharptest 答案中的一般模式。围绕处置设计一个基类有点限制,但是如果您使用 vb.net 或者不介意某些带有线程静态变量的游戏,那么专门设计的基类将可以注册变量以进行处置,甚至当它们在字段初始值设定项或派生类构造函数中创建时(通常,如果在字段初始值设定项中引发异常,则无法处置任何已分配的 IDisposable 字段,并且如果派生类的构造函数抛出异常,则会出现部分创建的基础对象无法自行处置)。
不过,我不会打扰您的非托管资源列表。具有终结器的类不应保留对终结不需要的任何对象的引用。相反,终结所需的内容应该放在它自己的类中,并且“主”类应该创建后一个类的实例并保留对它的引用。
I like the general pattern from csharptest's answer. Having a base class designed around disposal is a bit limiting, but if you're using vb.net or don't mind some games with thread-static variables, a specially-designed base class will make it possible to register variables for disposal even when they're created in field initializers or derived-class constructors (normally, if an exception is thrown in a field initializer, there's no way to dispose any already-allocated IDisposable fields, and if the constructor of a derived class throws an exception there's no way for the partially-created base object to dispose itself).
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.