如果我尝试编译这段代码,我会得到一个"使用未分配的局部变量"为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
已设置。
那是怎么回事?
当然我可以通过在顶部设置data
到null
来编译它,但是我很担心编译器看到的东西我不是,而且那个当我致电data
时,SaveData
可能没有任何明智之处。
我在.Net framework 4.6.01055中使用Visual Studio 2015(更新1)。
答案 0 :(得分:5)
问题是&&
是短路运营商。考虑您的第一个代码段
purgeData = foo &&
dataQueue.TryPeek(out data) &&
data.attempt.startTime.AddMilliseconds(timeout) < DateTime.Now &&
dataQueue.TryDequeue(out data);
由于任何原因foo
应该为假,因此不会评估条件中的其他语句。因此,TryPeek
和TryDequeue
都不会运行。虽然在您的示例中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。简而言之,它意味着:如果您可以在某些步骤(true
或false
)找到逻辑语句的结果,则可以跳过其余的表达式。
例如,在您的情况下,您有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);
您可以看到,如果foo
为false
,则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);