"使用未分配的局部变量"前后矛盾

时间:2016-02-16 02:49:44

标签: c#

如果我尝试编译这段代码,我会得到一个"使用未分配的局部变量"为data。 (显然foo = true最初更复杂。)

CuppingAttemptData data;
bool purgeData = true;
bool foo = true;

purgeData = foo &&
            dataQueue.TryPeek(out data) &&
            data.attempt.startTime.AddMilliseconds(timeout) < DateTime.Now &&
            dataQueue.TryDequeue(out data);


if (purgeData)
{
    DataSerialization.SaveData(new CowID("Missing", "Missing"), data);
}

有问题的用法是在倒数第二行:data调用结束时的变量SaveData。乍一看似乎编译器无法确保TryDequeue保证设置数据,但此代码确实编译:

CuppingAttemptData data;
bool purgeData = true;

purgeData = dataQueue.TryPeek(out data) &&
            data.attempt.startTime.AddMilliseconds(timeout) < DateTime.Now &&
            dataQueue.TryDequeue(out data);


if (purgeData)
{
    DataSerialization.SaveData(new CowID("Missing", "Missing"), data);
}

即。没有我琐碎的额外条件,编译器可以证明data已设置。

那是怎么回事?

当然我可以通过在顶部设置datanull来编译它,但是我很担心编译器看到的东西我不是,而且那个当我致电data时,SaveData可能没有任何明智之处。

我在.Net framework 4.6.01055中使用Visual Studio 2015(更新1)。

3 个答案:

答案 0 :(得分:5)

问题是&&是短路运营商。考虑您的第一个代码段

purgeData = foo &&
            dataQueue.TryPeek(out data) &&
            data.attempt.startTime.AddMilliseconds(timeout) < DateTime.Now &&
            dataQueue.TryDequeue(out data);

由于任何原因foo应该为假,因此不会评估条件中的其他语句。因此,TryPeekTryDequeue都不会运行。虽然在您的示例中foo永远不会为假,但编译器无法通过一个非平凡的示例来了解它。也许甚至没有一个简单的例子,尽管像ReSharper这样更聪明的实用程序可能会抓住它。

您的第二个代码段会编译,因为TryPeek也可以保证返回数据。它实际上与TryDequeue完全无关。

purgeData = dataQueue.TryPeek(out data) &&
            data.attempt.startTime.AddMilliseconds(timeout) < DateTime.Now &&
            dataQueue.TryDequeue(out data);

答案 1 :(得分:1)

  

乍一看似乎编译器看不到TryDequeue保证设置

哦,几乎可以肯定。这是C#编译器将省略不必要的检查的表达式,所以如果你在foo &&之后做了一些有效的事情,那么编译器会编译它,好像foo &&不存在一样。实际上很可能就好像foo根本不存在(无论如何都是在发布版本中)。

但这是在编译器级别,并且代码中的错误处于语言级别。

考虑一下,可以删除foo的事实不是语言的一个特征,而是基于最终结果的优化。

就语言的C#规则而言,foo是一个变量。作为变量,它的值可能会有所不同。由于它可能会有所不同,依赖它的分支可能会也可能不会被采用。

事实上,如果将其捕获到异步执行的代码段中,它就不会完全改变。

是的,你可以说“但它并没有被捕获到一段异步执行的代码中”,但你必须要考虑的不仅仅是那段代码才能知道。

编程语言首先是人,计算机是第二。应该可以查看一段代码并知道它意味着什么,这里的代码意味着可能访问未分配的变量。对于一个聪明的编译器来说,改变程序的运行方式非常有用,但不是它意味着什么:事实上,这本身就是对不良优化的测试,如果它改变了意义,那就不是优化,这是一个错误。

如果允许,也要考虑。如果foo被分配了比较计算23的阶乘并且看它是否大于2到45的幂,是否会被允许?这总是正确的,那么是否应该考虑foo &&之后的分支?如果我们改变规则,“必须没有可以遵循的分支,将变量保留为未分配”,“必须没有可以遵循的分支,保持变量未分配,聪明地检查那些分支”,那么什么级别我们应该有“聪明”吗?我们如何表达这种智能水平,以便程序员能够知道它是什么?

根据其逻辑结论,我们必须允许C#程序在使用一个编译器编译时有效,但在使用不太智能的编译器编译时无效。 C#不再是单一的编程语言,而是一系列大多数但并非完全兼容的方言。如果可能的话,应该避免这种情况。

即使所有的编译器都那么聪明,我们也不希望它们比我们在这方面更聪明(尽管我们肯定会在其他一些编译器中做到这一点)。如果一个人无法知道自己是否有效,那么如何编写一个程序呢?

总之有意义的是,在编译时,在这方面只能考虑常量表达式。

出于这个原因,如果您将foo更改为const bool foo = true;,那么它确实会编译。

答案 2 :(得分:0)

您应该阅读short-circuit evaluation。简而言之,它意味着:如果您可以在某些步骤(truefalse)找到逻辑语句的结果,则可以跳过其余的表达式。

例如,在您的情况下,您有4个表达式:

  • 我会称之为x, y, z, t
  • 您的逻辑陈述是:x && y && z && t
  • 如果x is false,则整个陈述为false。因此,我们不必担心y, z, t的价值。所以,他们将被跳过。

现在,在您的情况下:

purgeData = foo &&
            dataQueue.TryPeek(out data) &&
            data.attempt.startTime.AddMilliseconds(timeout) < DateTime.Now &&
            dataQueue.TryDequeue(out data);

您可以看到,如果foofalse,则data将被取消分配,因为其余的表达式未被评估。

如果您想解决此问题,请将其分为2个语句:

purgeData = dataQueue.TryPeek(out data) &&
            data.attempt.startTime.AddMilliseconds(timeout) < DateTime.Now &&
            dataQueue.TryDequeue(out data);
purgeData = purgeData && foo;

或者只是将表达式赋值给data移到第一位,以确保它会被执行。

purgeData = dataQueue.TryPeek(out data) &&
            data.attempt.startTime.AddMilliseconds(timeout) < DateTime.Now &&
            dataQueue.TryDequeue(out data)
            && foo;

或在评论中使用&作为Backwards_Dave的建议:

purgeData = foo &
            dataQueue.TryPeek(out data) &&
            data.attempt.startTime.AddMilliseconds(timeout) < DateTime.Now &&
            dataQueue.TryDequeue(out data);