【问题标题】:How to force an invariant method throw a particular exception in C# Code Contracts?如何强制不变方法在 C# 代码合同中引发特定异常?
【发布时间】:2013-12-22 10:46:42
【问题描述】:

我希望我的对象不变方法抛出一个特定的异常。是否有意义?在 C# 中可能吗?

例如,我有以下代码,包括具有不变方法的类 A 和异常类 E。目前 E 类不参与 A...

class A {
    int x = 0, y = 1;

    [ContractInvariantMethod]
    private void YisGreaterThanX() {
        Contract.Invariant(x < y);
    }
}

class E : Exception {
}

我需要的是以下内容。像 Contract.Requires 一样,拥有 Contract.Invariant(或者可能是一个属性构造函数,它接受异常派生类)会很有用。

class A {
    int x = 0, y = 1;

    [ContractInvariantMethod]
    private void YisGreaterThanX() {
        Contract.Invariant<E>(x < y);
    }
}

class E : Exception {
}

这是一个好主意吗?可能是我的逻辑错了?

【问题讨论】:

  • Contract.Requires 可以抛出异常以指示调用者代码中的错误。如果Contract.Invariant 抛出,那几乎总是表明您的代码中存在错误。你能详细说明你为什么要这样做吗?
  • @hvd,感谢您的回复。这是否意味着应该在每个public A(int x, int y) 类型的构造函数和每个类似的方法中包含Contract.Requires&lt;E&gt;(x &lt; y)?还是说A类的逻辑被破坏了?..
  • 是的,在那种情况下,我肯定会把它放在构造函数中。它是无效参数的组合,最好由 ArgumentException 指示,而不是随后变得不可用的类的实例。
  • 将其作为构造函数的要求也有助于文档:调用构造函数的代码知道x &lt; y 是该构造函数的要求。 (出于某种原因,即使过去不到五分钟,SO 也不允许我编辑我之前的评论以包含此内容。)
  • 非常有帮助的 cmets。但是您的建议需要重复代码(在所有类似方法中都类似Contract.Requires),不是吗?我应该写像Contract.Requires&lt;ArgumentException&gt;(Requirement(x, y)); 这样的东西,其中Requirement 是在类A 中定义的,比如public Func&lt;int, int, bool&gt; Requirement { get { return (x, y) =&gt; x &lt; y; } }

标签: c# exception code-contracts throw invariants


【解决方案1】:

鉴于我们不应该是catching Contract failures,并且由于没有明确指定异常的overload for Contract.Invariant,所以这个答案希望是假设性的。

因此,在所有免责声明之后,您可以通过连接ContractFailed 事件处理程序来破解以下内容:

Contract.ContractFailed += Contract_ContractFailed();

在处理程序中,过滤Invariant 失败,处理失败并重新抛出异常:

public static void Contract_ContractFailed(object sender,  ContractFailedEventArgs e)
{
    if (e.FailureKind == ContractFailureKind.Invariant)
    {
        e.SetHandled();
        throw new E(e.Message, e.OriginalException);
    }
}

鉴于您无法在 Contract.Invariant 定义中传递太多信息,如果您需要参数化抛出的异常,您需要将预期的异常编码为例如bool, string Contract.Invariant overload,或者使用外部全局状态,比如线程本地存储。

所有这些都是臭的 IMO。因此,要回答您的最后一个问题,我认为抛出可捕获的异常根本不是一个好主意 - 您的代码被调用超出其设计的状态范围,因此某处缺少错误/验证。

编辑
随后注意到,ContractFailed 处理程序中的处理+投掷仍然包裹在内部ContractException 中。所以你需要解压,例如

catch(Exception ex)
{
   var myException = ex.InnerException;
   // ... do something
}

【讨论】:

  • 感谢您的回复。例如,如果有人使用您建议的“臭味”技术,他无法在代码 try { var a = new A(0, 0); } catch (Exception e) { var x = e.Message; } 的 catch 块中捕获异常?
  • 通过处理ContractFailed,设置SetHandled,然后重新引发另一个异常,实际上它允许您将convert失败定义为可捕获的异常,所以是的,您可以在这个周围放一个try / catch。这很臭,因为它违背了ContractException 的“致命”和无法捕获的目的。
  • 但是代码try { var a = new A(0, 0); } catch (Exception e) { var x = e.Message; } 没有捕捉到异常。调试器在您的答案中的 public static void Contract_ContractFailed(object sender, ContractFailedEventArgs e) 方法的最后一个大括号处停止,并显示有关未处理异常 E 的通知。我错过了什么?
  • 可能只是你在VS中的调试设置?即它正在破坏所有 Ex 的,而不仅仅是未处理?
  • 对不起,我最近的评论有误。此代码按预期工作,并且调试器设置正确。不知何故,我认为调试器不应该在将进一步处理的异常情况下运行异常助手。你的假设帮助我理解了这一点。非常感谢您的回答。
猜你喜欢
  • 1970-01-01
  • 2018-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多