我将代码放在下面,并且还上传到了在线C#编译器中: jdoodle.com/a/1jww 该代码可以编译并在线运行,但是无法在本地Visual Studio中编译。
我正在使用:
Visual Studio 2017 15.9.13,
控制台应用程序,.Net Framework 4.7.2
语言版本C#7.3
Microsoft(R)Visual C#编译器版本2.10.0.0(b9fb1610)
下面是代码:
class Program
{
static void Main()
{
Dictionary<string,int> myDict = new Dictionary<string,int>();
myDict.Add("hello", 1);
if (myDict?.TryGetValue("hello", out var value) == true)
{
Console.WriteLine("Hello" + value.ToString());
}
}
}
期望在Console.Output中看到Hello1 因为如果条件为true,则Null-Conditional检查必须返回一个非null值,并且字典中存在键,并且从TryGetValue方法返回时必须已分配该值。
因为根据the documentation:
被调用的方法需要在方法返回之前分配一个值。
更新:
这是https://github.com/dotnet/roslyn/issues/32572中的一个未解决问题
我认为,如果编译器的要求包括不发出错误警报,那将是一个真正的问题/错误。
我的论点:
每当CPU执行到if括号代码块内的点时,该值必须已从TryGetValue调用返回,并且不是“未分配的局部变量”。如果编译器在解释空条件运算符时无法期待分配状态,则一种更简单的解决方案是给“无法确定分配状态”之类的警告而不是错误。
答案 0 :(得分:3)
这是由于编译器差异造成的。
在https://dotnetfiddle.net/5GgGNS这个小提琴中,您可以看到错误,该错误在mono编译器中被忽略。
由于该行这一事实,我认为该错误是有效的
if (myDict?.TryGetValue("hello", out var value) == true)
不保证初始化局部变量value
。
如果您将其重写为:
if (myDict?.TryGetValue("hello", out var value) == null)
它将尝试访问value
。
现在,null
值或您的情况下的true
可能是函数的返回值,只有在运行时才能知道。
但是,由于所有变量基本上都是always initialized,因此它只是编译器功能。
另一方面,根据C#5规范:
由局部变量声明引入的局部变量不会自动初始化,因此没有默认值。出于确定分配检查的目的,由local-variable-declaration引入的局部变量最初被视为未分配。局部变量声明可以包含局部变量初始化器,在这种情况下,只有在初始化表达式(第5.3.3.4节)之后,该变量才被视为明确赋值。
但是您的代码是C#6。
所以我的结论是,编译器对它的解释不同。 Microsoft编译器将?.
运算符考虑在内。您应该将其归档为错误,或者至少在双方都找到。
有趣的事实,如果您使用此代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
class Program
{
static void Main()
{
//Your code goes here
Dictionary<string,int> myDict = null;
if (myDict?.TryGetValue("hello", out var value) == null)
{
Console.WriteLine("Hello" + value.ToString());
}
}
}
[使用https://www.jdoodle.com/compile-c-sharp-online,单声道5.10.1]
您将在工作中看到对default(T)
的实际初始化。输出为Hello0
。不过,这很了不起,因为由于?
以及myDict
是null
的事实,不应调用TryGetValue
并留下value
“未初始化” 。
空条件运算符正在短路。也就是说,如果条件成员或元素访问操作链中的一个操作返回null,则该链的其余部分将不执行。
但是... ,因为没有未初始化的变量;如果编译成功,编译器将确保其行为未定义。
因此,由于value
已初始化,因此在运行时,问题在于在构建时是否是有效的编译器错误。关于代码的运行时意图,这是(这就是为什么错误首先出现在这里的原因),但是我认为它仍然是灰色区域。
请注意,根据this default(T)
是不可覆盖的,实际上不会导致失败的任何情况。
通过运行此小测试:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
class Program
{
static void Main()
{
//Your code goes here
Dictionary<string,int> myDict = null;
if (myDict?.Bar(out var test) == null)
{
Console.WriteLine("does hit");
}
}
}
static class Foo
{
public static object Bar(this Dictionary<string,int> input, out int test)
{
test = 3;
Console.WriteLine("does not hit");
return 1;
}
}
[使用https://www.jdoodle.com/compile-c-sharp-online,单声道5.10.1]
输出变为:
does hit
您可以验证?.
运算符的正确运行时行为。
答案 1 :(得分:2)
空条件?.
消除了保证将value
分配给的保证,因为TryGetValue
仅在myDict
不是null
时才有条件地被调用。 / p>
之后,您需要在value
内的if
语句中强制== true
进行赋值,因为如果由于以下原因未调用null
,则左侧将返回TryGetValue
。 myDict
本身为空。但是,在一般情况下,编译器无法做出这种推断,因此您必须通过预先测试myDict
中的null
(并跳过?.
)来解决这个问题。或< / strong>预先初始化value
。
答案 2 :(得分:0)
我建议在您的“ TryGetValue”调用之前初始化value变量。
答案 3 :(得分:0)
空条件运算符引入了一条不同的执行路径,从而使TryGetValue
短路。但是,value
变量仍然存在,因为它的作用域位于if
和TryGetValue
语句之外,这表明语法糖的这种风味必须导致变量之前的良好旧声明。使用。例如:
if (myDict?.TryGetValue("hello", out var value) == true)
{
Console.WriteLine("Hello" + value.ToString());
}
//Current issue aside, this is legal as the variable exists at a higher scope,
//beyond the if and TryGetValue statements although the brackets give the illusion of scope.
value = 2;
应等同于:
int value;
if (myDict?.TryGetValue("hello", out value) == true)
{
Console.WriteLine("Hello" + value.ToString());
}
value = 2;
这还会导致编译错误。
但是,如果myDict
是null
,并且您没有使用空条件运算符,则会对Dictionary<string, int> myDict; //Not initialized
//You would get the warning for myDict here instead
if (myDict.TryGetValue("hello", out var value) == true)
{
Console.WriteLine("Hello" + value.ToString());
}
发出相同的错误:
myDict
换句话说,null
永远不可能 is_feasible
且编译时不会出现相同的错误,因此无需使用空条件运算符。这就是为什么删除它可以解决此问题。
话虽这么说,the documentation指出:
无需分配初始值。通过声明out变量 在方法调用中使用它的地方,您不能在之前不小心使用它 它被分配了。
因此,从理论上讲,由于语言构造允许您在分配变量之前意外地使用它。但是,编译器正在从较低级别识别问题,而不会让您解决。
在我看来,这是语法糖雾掩盖的有效编译器验证。