使用 IDisposable 检查约束——疯狂还是天才?
我在今天正在处理的代码库中遇到了一个模式,最初看起来非常聪明,后来让我发疯,现在我想知道是否有一种方法可以挽救聪明的部分,同时最大限度地减少疯狂。
我们有一堆实现 IContractObject
的对象,以及一个如下所示的 InvariantChecker
类:
internal class InvariantChecker : IDisposable
{
private IContractObject obj;
public InvariantChecker(IContractObject obj)
{
this.obj = obj;
}
public void Dispose()
{
if (!obj.CheckInvariants())
{
throw new ContractViolatedException();
}
}
}
internal class Foo : IContractObject
{
private int DoWork()
{
using (new InvariantChecker(this))
{
// do some stuff
}
// when the Dispose() method is called here, we'll throw if the work we
// did invalidated our state somehow
}
}
它用于提供相对轻松的状态一致性运行时验证。这不是我写的,但最初看起来这是一个很酷的主意。
但是,如果 Foo.DoWork 抛出异常,就会出现问题。当抛出异常时,我们很可能处于不一致的状态,这意味着 InvariantChecker 也会抛出,隐藏原始异常。当异常在调用堆栈中向上传播时,这种情况可能会发生多次,每个帧上都有一个 InvariantChecker 来隐藏下面帧中的异常。为了诊断问题,我必须禁用 InvariantChecker 中的 throw,然后才能看到原始异常。
这显然是可怕的。然而,有没有什么方法可以挽救最初想法的巧妙之处,而又不会出现可怕的异常隐藏行为?
I ran across a pattern in a codebase I'm working on today that initially seemed extremely clever, then later drove me insane, and now I'm wondering if there's a way to rescue the clever part while minimizing the insanity.
We have a bunch of objects that implement IContractObject
, and a class InvariantChecker
that looks like this:
internal class InvariantChecker : IDisposable
{
private IContractObject obj;
public InvariantChecker(IContractObject obj)
{
this.obj = obj;
}
public void Dispose()
{
if (!obj.CheckInvariants())
{
throw new ContractViolatedException();
}
}
}
internal class Foo : IContractObject
{
private int DoWork()
{
using (new InvariantChecker(this))
{
// do some stuff
}
// when the Dispose() method is called here, we'll throw if the work we
// did invalidated our state somehow
}
}
This is used to provide a relatively painless runtime validation of state consistency. I didn't write this, but it initially seemed like a pretty cool idea.
However, the problem arises if Foo.DoWork
throws an exception. When the exception is thrown, it's likely that we're in an inconsistent state, which means that the InvariantChecker
also throws, hiding the original exception. This may happen several times as the exception propagates up the call stack, with an InvariantChecker
at each frame hiding the exception from the frame below. In order to diagnose the problem, I had to disable the throw in the InvariantChecker
, and only then could I see the original exception.
This is obviously terrible. However, is there any way to rescue the cleverness of the original idea without getting the awful exception-hiding behavior?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
我不喜欢以这种方式重载
using
的含义。为什么不使用采用委托类型的静态方法呢?所以你可以这样写:或者甚至更好,只需将其设为扩展方法即可:(
请注意,需要“this”部分才能让 C# 编译器查找适用于
this
.) 这还允许您使用“正常”方法来实现操作(如果需要),并使用方法组转换为其创建委托。如果您有时想从正文返回,您可能还希望允许它返回一个值。现在 CheckInvariantActions 可以使用类似的东西:
我还建议
CheckInvariants
应该直接抛出异常,而不是仅仅返回bool
- 这样异常可以提供有关 哪个不变量被违反了。I don't like the idea of overloading the meaning of
using
in this way. Why not have a static method which takes a delegate type instead? So you'd write:Or even better, just make it an extension method:
(Note that the "this" part is needed in order to get the C# compiler to look for extension methods that are applicable to
this
.) This also allows you to use a "normal" method to implement the action, if you want, and use a method group conversion to create a delegate for it. You might also want to allow it to return a value if you would sometimes want to return from the body.Now CheckInvariantActions can use something like:
I would also suggest that
CheckInvariants
should probably throw the exception directly, rather than just returningbool
- that way the exception can give information about which invariant was violated.这是对使用模式的可怕滥用。使用模式是为了处理非托管资源,而不是为了像这样的“聪明”技巧。我建议直接编写代码。
This is a horrid abuse of the using pattern. The using pattern is for disposing of unmanaged resources, not for "clever" tricks like this. I suggest just writing straight forward code.
如果您真的想要这样做:
If you really want to do this:
而不是这样:
只需执行此操作(假设您没有从
do some stuff
返回):如果您需要从
do some stuff
返回,我相信一些重构是有序的:它更简单,你不会遇到问题你之前有过。
你只需要一个简单的扩展方法:
Instead of this:
Just do this (assuming you don't return from
do some stuff
):If you need to return from
do some stuff
, I believe some refactoring is in order:It's simpler and you won't have the problems you were having before.
You just need a simple extension method:
向
InvariantChecker
类添加一个属性,允许您禁止检查/抛出。Add a property to the
InvariantChecker
class that allows you to suppress the check/throw.如果您当前的问题是获取原始异常 - 转到调试 ->异常并检查所有 CLR 异常的“抛出”情况。当抛出异常时它会中断,因此你会首先看到它。如果从 VS 的角度来看“不是您的代码”抛出异常,您可能还需要关闭工具->选项->调试->“仅我的代码”选项。
If you current problem is to get original exception - go to Debug -> Exceptions and check "thrown" for all CLR exceptions. It will break when exception is thrown and as result you'll see it first. You may need to also turn off tools->options->debug->"my code only" option if exceptions are throw from "not your code" from VS point of view.
要做到这一点,需要一种干净的方法来确定调用 Dispose 时是否有异常处于待处理状态。要么微软应该提供一种标准化的方法来随时查明当前 try-finally 块退出时将出现哪些异常(如果有),要么微软应该支持扩展的 Dispose 接口(可能是 DisposeEx,它将继承 Dispose)接受待处理异常参数。
What is needed to make this nice is a clean means of finding out whether an exception is pending when Dispose is called. Either Microsoft should provide a standardized means of finding out at any time what exception (if any) will be pending when the current try-finally block exits, or Microsoft should support an extended Dispose interface (perhaps DisposeEx, which would inherit Dispose) which would accept a pending-exception parameter.