8

使用此构造:

var dict = new Dictionary<int, string>();
var result = (dict?.TryGetValue(1, out var value) ?? false) ? value : "Default";

我收到一条错误消息,说CS0165 use of unassigned local variable 'value'这不是我所期望的。怎么value可能是未定义的?如果字典为 null,则内部语句将返回false,这将使外部语句评估为 false,返回Default.

我在这里想念什么?仅仅是编译器无法完全评估该语句吗?还是我以某种方式搞砸了?

4

2 回答 2

11

你的分析是正确的。这不是编译器进行的分析,因为编译器进行了 C# 规范所要求的分析。该分析如下:

  • 如果condition?consequence:alternative表达式的条件是编译时常量true,则替代分支不可到达;如果false,则结果分支不可到达;否则,两个分支都可以到达。

  • 这种情况下的条件不是一个常数,因此结果和替代都是可以达到的。

  • 局部变量value只有dict在不为空时才被明确赋值,因此在达到结果value不会被明确赋值

  • 但后果要求value明确指定

  • 所以这是一个错误。

编译器没有你聪明,但它是 C# 规范的准确实现。(请注意,我没有在这里勾勒出针对这种情况的额外特殊规则,其中包括诸如“在真表达式之后肯定分配”等谓词。有关详细信息,请参阅 C# 规范。)

顺便说一句,C# 2.0 编译器太聪明了。例如,如果您有类似0 * x == 0某个 int local的条件,x它会推断“无论值x是什么,该条件始终为真”并将替代分支标记为不可访问。从与现实世界匹配的意义上说,该分析是正确的,但从 C# 规范明确表示仅针对编译时常量进行推导,并且同样明确表示涉及变量的表达式是不是恒定的。

记住,这个东西的目的是为了找bug,还有什么可能性更大呢?有人写0 * x == 0 ? foo : bar的意图是它具有“总是foo”的含义,或者他们偶然写了一个错误?我修复了编译器中的错误,从那时起它就严格符合规范。

在您的情况下,没有错误,但是代码对于编译器而言太复杂而无法分析,因此期望人类进行分析可能也太复杂了。看看能不能简化。我可能会做的是:

public static V GetValueOrDefault<K, V>(
  this Dictionary<K, V> d,
  K key,
  V defaultValue)
{
    if (d != null && d.TryGetValue(key, out var value))
      return value;
    return defaultValue;
}
…
var result = dict.GetValueOrDefault(1, "Default");

目标应该是使调用站点可读;我认为我的呼叫站点比您的更具可读性。

于 2019-03-17T15:06:15.447 回答
4

仅仅是编译器无法完全评估该语句吗?

是的,或多或少。

编译器不跟踪未分配,它跟踪相反的“明确分配”。它必须在某个地方停止,在这种情况下,它需要合并有关库方法的知识TryGetValue()。它没有。

于 2019-03-17T12:36:12.177 回答