【问题标题】:convincing C# compiler that execution will stop after a member returns说服 C# 编译器在成员返回后停止执行
【发布时间】:2011-01-31 10:51:04
【问题描述】:

我认为这目前是不可能的,或者甚至是一个好主意,但这是我刚才正在考虑的事情。我使用 MSTest 对我的 C# 项目进行单元测试。在我的一项测试中,我执行以下操作:

MyClass instance;

try
{
    instance = getValue();
}
catch (MyException ex)
{
    Assert.Fail("Caught MyException");
}

instance.doStuff(); // Use of unassigned local variable 'instance'

要编译这段代码,我必须在instance 的声明或catch 块中为其赋值。我也可以在Assert.Fail 之后使用return,但这仍然是一种解决方法,而不是编译器只是知道在此之后无法继续执行。据我所知,Assert.Fail 永远不会允许执行越过它,因此instance 永远不会在没有值的情况下使用。那为什么我必须给它赋值呢?如果我将Assert.Fail 更改为throw ex 之类的东西,代码编译得很好,我假设因为它知道异常将不允许执行继续到instance 将被使用未初始化的点。

相反,如果我不想让测试失败,而是被标记为不确定,该怎么办?我可以用Assert.Inconclusive 代替Fail,如果编译器知道在那之后不会继续执行就好了。

那么,关于允许执行在哪里进行,这是运行时与编译时知识的情况吗? C# 以某种方式说成员(在这种情况下为 Assert.Fail)在返回后将永远不允许执行,这是否合理?也许这可能是方法属性的形式。这对编译器有用还是不必要的复杂性?

外部单元测试

由于人们 [有效地] 指出这是编写单元测试的一种愚蠢方式,因此请在单元测试领域之外考虑我的问题:

MyClass instance;

if (badThings)
{
    someMethodThatWillNeverReturn();
}
else
{
    instance = new MyClass();
}

instance.doStuff();

在这里,我可能会用抛出异常来替换对someMethodThatWillNeverReturn 的调用,也许如果我有事情要做,我可以在异常的构造函数中完成。

Resharper 知道

如果我在Assert.FailAssert.Inconclusive 之后添加return,Resharper 会将return 涂成灰色,并有一个工具提示说“代码是启发式无法访问的。”

【问题讨论】:

  • @SLaks:我想既然是在征求大家的意见,那就是主观的。我很确定我所说的内容目前不受支持,所以我主要是在问它是否是一个有用的功能。那不会有一个客观的答案。
  • @Kobi:呃,积分就是积分;这不是世界末日。如果这个线程产生有用的讨论,那就更重要了(IMO)。 YMMV。 :-P
  • 我们不要为了让程序员编写懒惰/糟糕的单元测试而创建额外的语法和函数头的额外属性。
  • @Sky:这就是我将问题标记为社区 wiki 的原因。 :) Jon Skeet 说“是的,这将是合理的”,而您认为这很荒谬。这是一个主观问题!
  • 主观:是的。斯基特对这种东西的影响和潜在效用的理解比我的工资等级高几个。只是发表我的主观意见。 p.s.去西方!大声笑

标签: c# compiler-construction runtime execution compile-time


【解决方案1】:

是的,有一些东西表明一个成员永远不会正常完成是合理的 - 即断言该成员之后的点是不可到达的。 (这可能是由于异常或由于永远循环。)

你会希望有 一些东西(无论是在 CLR 还是编译器中)来为你的错误提供一个备份计划:如果有人改变了Assert.Fail 会发生什么正常返回?您可能希望部分代码验证是经过检查后永远不会正常返回的内容。

我相信微软的某个人有一篇关于这个想法的博客文章......我会看看能不能找到它。

就表示它的语法而言,虽然属性是一个显而易见的想法,但我非常喜欢返回类型“never”的想法。显然,这可能会与现有的“从不”类型发生冲突,但是嘿......

就其有用性而言:显而易见的解决方法是在语句之​​后立即抛出异常,但必须这样做肯定很烦人。 (它通常比返回更好,因为这意味着如果您编写它的方法具有返回类型,您不必指定无意义的返回值 - 而且您也不需要确保所有 @987654322 @ 参数被赋值。)所以它不是必需的 - 但我认为它会很好。这是否是 C# 团队在有限预算下能做的最重要的事情是另一回事 - 只是为了抢占 Eric ;)

【讨论】:

  • @Sarah:但我的意思是编译器会相信方法真的永远不会返回。如果它是第三方库中的方法怎么办?编译器如何验证它永远不会返回?
  • 我认为 Jon 指出的问题在于,它与 C 或 C++ 不同(如果在编译源文件后更改头文件,所有赌注都没有了,所以程序员学会了不要这样做),在 .NET 中编译的代码单元不受同样严格的约束:如果 Assert.Failnoreturn,例如调用另一个函数 noreturn,然后您稍后重新编译该函数以不再是 @ 987654326@,系统很难验证Assert.Failnoreturn-ness 是否仍然有效(没有重新编译)。
  • 我认为如果方法实际上确实返回,那么编译器(或可能是运行时)自动插入代码以引发异常实际上会更简单。
  • Netscape 的家伙为 ECMAScript 提出了一个“从不”返回类型,它具有所需的语义;可以假设这样的方法总是要么永远运行要么抛出异常。我们没有计划将这种类型添加到 C# 或 CLR 类型系统中;拥有它会很好,但它实际上并没有给你带来太多。
  • Re:检测到这个:当然,这很容易。我们已经必须实现检测非 void 方法是否具有可到达端点的代码。 “从不”方法只是一个 void 方法,不允许有可到达的端点或任何返回语句。这解决了编译时的问题。在运行时,验证者有责任确保方法真正正确地实现其返回类型语义;验证者可以类似地确定没有返回指令并且端点不可到达。
【解决方案2】:

throw exAssert.Fail 之后?或者,更好的是,在这种情况下完全删除try/catchAssert.Fail,让未捕获的异常为您进行单元测试失败。

【讨论】:

【解决方案3】:

我想知道的一件事是:如果您手动编译并执行此代码,运行时将如何处理它?我相信它会检查 IL 代码中的错误,也许它也能捕捉到这个“错误”......也许不会:)

【讨论】:

    【解决方案4】:

    returnAssert.Fail() 之后

    【讨论】:

    • 如果函数应该返回一些没有默认构造函数的对象,写起来可能会很烦人。
    • 这只是另一种解决方法,例如将null 分配给instance。我要问的是编译器是否可以知道Assert.Fail 在完成后不会继续执行,所以不需要return
    • 我怀疑你会找到一种方法让编译器知道这一点,但如果你这样做了,我会很想听听它是什么。我怀疑您最好的选择是封装异常的构造并抛出该异常。所以throw MyFailer.GetFailure("The reason it failed").
    【解决方案5】:

    看到你在抛出异常时断言失败,为什么不把整个测试移到try中,或者去掉try,让抛出的异常自动断言失败。

    try
    {
        MyClass instance;
        instance = MyClass.getValue();
        instance.doStuff();
    }
    catch (Exception ex)
    {
        Assert.Fail("Caught MyException");
    }
    

    ...或...

    MyClass instance;
    instance = MyClass.getValue();
    instance.doStuff();
    

    【讨论】:

    • 这两种解决方案的语义都与原始帖子不同。
    • 是的,但是就写一个好的单元测试而言,原帖的语义是错误的。
    • @Nick:我同意它们是不同的,但是这两个示例都展示了一种无需对语言和运行时进行根本更改即可获得相似结果的方法。在声明时将值设置为 null(或其他默认值)也可以。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-24
    相关资源
    最近更新 更多