我在今天工作的代码库中遇到了一个模式,最初看起来非常聪明,后来让我疯了,现在我想知道是否有办法拯救聪明的部分,同时尽量减少疯狂。< / p>
我们有一堆实现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
中的抛出,然后才能看到原始异常。
这显然很可怕。但是,有没有办法拯救原始想法的聪明性而不会出现可怕的异常隐藏行为?
答案 0 :(得分:9)
我不喜欢以这种方式重载using
的含义的想法。为什么不使用一个代理类型的静态方法呢?所以你要写:
InvariantChecker.Check(this, () =>
{
// do some stuff
});
甚至更好,只需将其作为扩展方法:
this.CheckInvariantActions(() =>
{
// do some stuff
});
(注意,需要“this”部分才能让C#编译器查找适用于this
的扩展方法。)这也允许您使用“普通”方法来实现动作,如果需要,并使用方法组转换为其创建委托。如果您有时想要从正文中返回,您可能还希望允许它返回一个值。
现在CheckInvariantActions可以使用类似的东西:
action();
if (!target.CheckInvariants())
{
throw new ContractViolatedException();
}
我还建议CheckInvariants
应该直接抛出异常,而不是仅返回bool
- 这样异常可以提供有关违反不变量的信息。
答案 1 :(得分:5)
这是对使用模式的可怕滥用。使用模式用于处理非托管资源,而不是像这样的“聪明”技巧。我建议只写直接的代码。
答案 2 :(得分:2)
如果真的想要这样做:
internal class InvariantChecker : IDisposable
{
private IContractObject obj;
public InvariantChecker(IContractObject obj)
{
this.obj = obj;
}
public void Dispose()
{
if (Marshal.GetExceptionCode() != 0xCCCCCCCC && obj.CheckInvariants())
{
throw new ContractViolatedException();
}
}
}
答案 3 :(得分:1)
而不是:
using (new InvariantChecker(this)) {
// do some stuff
}
这样做(假设你没有从do some stuff
返回):
// do some stuff
this.EnforceInvariants();
如果您需要从do some stuff
返回,我相信有些refactoring是有序的:
DoSomeStuff(); // returns void
this.EnforceInvariants();
...
var result = DoSomeStuff(); // returns non-void
this.EnforceInvariants();
return result;
它更简单,你不会遇到以前遇到的问题。
您只需要一个简单的扩展方法:
public static class InvariantEnforcer {
public static void EnforceInvariants(this IContractObject obj) {
if (!obj.CheckInvariants()) {
throw new ContractViolatedException();
}
}
}
答案 4 :(得分:0)
向InvariantChecker
类添加一个属性,允许您禁止check / throw。
internal class InvariantChecker : IDisposable
{
private IContractObject obj;
public InvariantChecker(IContractObject obj)
{
this.obj = obj;
}
public bool Suppress { get; set; }
public void Dispose()
{
if (!this.Suppress)
{
if (!obj.CheckInvariants())
{
throw new ContractViolatedException();
}
}
}
}
internal class Foo : IContractObject
{
private int DoWork()
{
using (var checker = new InvariantChecker(this))
{
try
{
// do some stuff
}
catch
{
checker.Suppress = true;
throw;
}
}
}
}
答案 5 :(得分:0)
如果您当前的问题是获得原始异常 - 请转到Debug - &gt;所有CLR异常的异常和检查“抛出”。它会在抛出异常时中断,因此您将首先看到它。您可能还需要关闭工具 - > gt; options-&gt; debug-&gt;“仅限我的代码”选项,如果从VS的角度看“非您的代码”抛出异常。
答案 6 :(得分:0)
使这个更好的需要是一种干净的方法,可以在调用Dispose时查明异常是否处于未决状态。要么Microsoft提供一种标准化的方法,可以随时查找当前try-finally块退出时将要挂起的异常(如果有),或者Microsoft应该支持扩展的Dispose接口(可能是DisposeEx,它将继承Dispose)接受pending-exception参数。