使用IDisposable检查约束 - 疯狂还是天才?

时间:2011-03-07 16:42:23

标签: c# exception idisposable

我在今天工作的代码库中遇到了一个模式,最初看起来非常聪明,后来让我疯了,现在我想知道是否有办法拯救聪明的部分,同时尽量减少疯狂。< / 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中的抛出,然后才能看到原始异常。

这显然很可怕。但是,有没有办法拯救原始想法的聪明性而不会出现可怕的异常隐藏行为

7 个答案:

答案 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参数。