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

发布于 2024-10-20 23:29:47 字数 1143 浏览 4 评论 0原文

我在今天正在处理的代码库中遇到了一个模式,最初看起来非常聪明,后来让我发疯,现在我想知道是否有一种方法可以挽救聪明的部分,同时最大限度地减少疯狂。

我们有一堆实现 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

浮华 2024-10-27 23:29:47

我不喜欢以这种方式重载 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 - 这样异常可以提供有关 哪个不变量被违反了。

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:

InvariantChecker.Check(this, () =>
{
    // do some stuff
});

Or even better, just make it an extension method:

this.CheckInvariantActions(() =>
{
    // do some stuff
});

(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:

action();
if (!target.CheckInvariants())
{
    throw new ContractViolatedException();
}

I would also suggest that CheckInvariants should probably throw the exception directly, rather than just returning bool - that way the exception can give information about which invariant was violated.

不回头走下去 2024-10-27 23:29:47

这是对使用模式的可怕滥用。使用模式是为了处理非托管资源,而不是为了像这样的“聪明”技巧。我建议直接编写代码。

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.

心欲静而疯不止 2024-10-27 23:29:47

如果您真的想要这样做:

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();
        }
    }
}

If you really want to do this:

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();
        }
    }
}
满天都是小星星 2024-10-27 23:29:47

而不是这样:

using (new InvariantChecker(this)) {
  // do some stuff
}

只需执行此操作(假设您没有从 do some stuff 返回):

// do some stuff
this.EnforceInvariants();

如果您需要从 do some stuff 返回,我相信一些重构是有序的:

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();
    }
  }
}

Instead of this:

using (new InvariantChecker(this)) {
  // do some stuff
}

Just do this (assuming you don't return from do some stuff):

// do some stuff
this.EnforceInvariants();

If you need to return from do some stuff, I believe some refactoring is in order:

DoSomeStuff(); // returns void
this.EnforceInvariants();

...

var result = DoSomeStuff(); // returns non-void
this.EnforceInvariants();
return result;

It's simpler and you won't have the problems you were having before.

You just need a simple extension method:

public static class InvariantEnforcer {
  public static void EnforceInvariants(this IContractObject obj) {
    if (!obj.CheckInvariants()) {
      throw new ContractViolatedException();
    }
  }
}
你丑哭了我 2024-10-27 23:29:47

InvariantChecker 类添加一个属性,允许您禁止检查/抛出。

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;
            }
        }
    }
}

Add a property to the InvariantChecker class that allows you to suppress the 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;
            }
        }
    }
}
空宴 2024-10-27 23:29:47

如果您当前的问题是获取原始异常 - 转到调试 ->异常并检查所有 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.

旧伤慢歌 2024-10-27 23:29:47

要做到这一点,需要一种干净的方法来确定调用 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文