空合并参数给出意外警告
Null-coalescing out parameter gives unexpected warning
使用这个构造:
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
怎么可能是未定义的?如果字典为空,则内部语句将 return false
这将使外部语句评估为 false,returning Default
.
我在这里错过了什么?仅仅是编译器无法完全评估语句吗?还是我搞砸了?
Is it just the compiler being unable to evaluate the statement fully?
是的,或多或少。
编译器不跟踪未分配的,它跟踪相反的 'defintely assigned'。它必须在某个地方停下来,在这种情况下,它需要合并有关库方法的知识 TryGetValue()
。它没有。
您的分析是正确的。它不是编译器进行的分析,因为编译器进行的分析是 C# 规范所要求的。即分析如下:
如果 condition?consequence:alternative
表达式的条件是一个编译时常量 true
那么替代分支是不可到达的;如果false
,则结果分支不可达;否则,两个分支都是可达的。
这种情况下的条件不是常数,因此结果和备选方案都是可达的。
局部变量value
只有在dict
不为空时才明确赋值,因此value
未明确赋值当后果达到时。
但结果要求value
肯定赋值
所以这是一个错误。
编译器没有你那么聪明,但它是C#规范的准确实现。 (请注意,我没有在这里勾画出针对这种情况的额外特殊规则,其中包括像 "definitely assigned after a true expression" 等谓词。有关详细信息,请参阅 C# 规范。)
顺便说一句,C# 2.0 编译器太聪明了。例如,如果你有一个像 0 * x == 0
这样的条件,对于 some int local x
它会推断出 "that condition is always true no matter what the value of x
is" 并将替代分支标记为不可访问。该分析在符合现实世界的意义上是正确的,但在 C# 规范明确指出仅对编译时常量进行推导并且同样明确指出涉及 [= 的表达式的意义上是不正确的45=]变量 不是常量。
记住,这东西的目的是找bug,还有什么更有可能呢?有人写 0 * x == 0 ? foo : bar
的意思是 "always 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");
目标应该是使调用站点可读;我认为我的呼叫站点比你的更具可读性。
使用这个构造:
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
怎么可能是未定义的?如果字典为空,则内部语句将 return false
这将使外部语句评估为 false,returning Default
.
我在这里错过了什么?仅仅是编译器无法完全评估语句吗?还是我搞砸了?
Is it just the compiler being unable to evaluate the statement fully?
是的,或多或少。
编译器不跟踪未分配的,它跟踪相反的 'defintely assigned'。它必须在某个地方停下来,在这种情况下,它需要合并有关库方法的知识 TryGetValue()
。它没有。
您的分析是正确的。它不是编译器进行的分析,因为编译器进行的分析是 C# 规范所要求的。即分析如下:
如果
condition?consequence:alternative
表达式的条件是一个编译时常量true
那么替代分支是不可到达的;如果false
,则结果分支不可达;否则,两个分支都是可达的。这种情况下的条件不是常数,因此结果和备选方案都是可达的。
局部变量
value
只有在dict
不为空时才明确赋值,因此value
未明确赋值当后果达到时。但结果要求
value
肯定赋值所以这是一个错误。
编译器没有你那么聪明,但它是C#规范的准确实现。 (请注意,我没有在这里勾画出针对这种情况的额外特殊规则,其中包括像 "definitely assigned after a true expression" 等谓词。有关详细信息,请参阅 C# 规范。)
顺便说一句,C# 2.0 编译器太聪明了。例如,如果你有一个像 0 * x == 0
这样的条件,对于 some int local x
它会推断出 "that condition is always true no matter what the value of x
is" 并将替代分支标记为不可访问。该分析在符合现实世界的意义上是正确的,但在 C# 规范明确指出仅对编译时常量进行推导并且同样明确指出涉及 [= 的表达式的意义上是不正确的45=]变量 不是常量。
记住,这东西的目的是找bug,还有什么更有可能呢?有人写 0 * x == 0 ? foo : bar
的意思是 "always 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");
目标应该是使调用站点可读;我认为我的呼叫站点比你的更具可读性。