Null-coalescing out parameter gives unexpected warning

南笙酒味 提交于 2019-12-30 18:39:33

问题


Using this construct:

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

I get an error saying CS0165 use of unassigned local variable 'value' which is not what I expect. How could value possibly be undefined? If the dictionary is null the inner statement will return false which will make the outer statement evaluate to false, returning Default.

What am I missing here? Is it just the compiler being unable to evaluate the statement fully? Or Have I messed it up somehow?


回答1:


Your analysis is correct. It is not the analysis the compiler makes, because the compiler makes the analysis that is required by the C# specification. That analysis is as follows:

  • If the condition of a condition?consequence:alternative expression is a compile-time constant true then the alternative branch is not reachable; if false, then the consequence branch is not reachable; otherwise, both branches are reachable.

  • The condition in this case is not a constant, therefore the consequence and alternative are both reachable.

  • local variable value is only definitely assigned if dict is not null, and therefore value is not definitely assigned when the consequence is reached.

  • But the consequence requires that value be definitely assigned

  • So that's an error.

The compiler is not as smart as you, but it is an accurate implementation of the C# specification. (Note that I have not sketched out here the additional special rules for this situation, which include predicates like "definitely assigned after a true expression" and so on. See the C# spec for details.)

Incidentally, the C# 2.0 compiler was too smart. For example, if you had a condition like 0 * x == 0 for some int local x it would deduce "that condition is always true no matter what the value of x is" and mark the alternative branch as unreachable. That analysis was correct in the sense that it matched the real world, but it was incorrect in the sense that the C# specification clearly says that the deduction is only to be made for compile-time constants, and equally clearly says that expressions involving variables are not constant.

Remember, the purpose of this thing is to find bugs, and what is more likely? Someone wrote 0 * x == 0 ? foo : bar intending that it have the meaning "always foo", or that they've written a bug by accident? I fixed the bug in the compiler and since then it has strictly matched the specification.

In your case there is no bug, but the code is too complicated for the compiler to analyze, so it is probably also too complicated to expect humans to analyze. See if you can simplify it. What I might do is:

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");

The goal should be to make the call site readable; I think my call site is considerably more readable than yours.




回答2:


Is it just the compiler being unable to evaluate the statement fully?

Yes, more or less.

The compiler does not track unassigned, it tracks the opposite 'defintely assigned'. It has to stop somewhere, in this case it would need to incorporate knowledge about the library method TryGetValue(). It doesn't.



来源:https://stackoverflow.com/questions/55207014/null-coalescing-out-parameter-gives-unexpected-warning

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!