为什么编译器会引发错误CS0165:使用未分配的局部变量?

时间:2019-06-26 19:28:45

标签: c# compiler-errors

我将代码放在下面,并且还上传到了在线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调用返回,并且不是“未分配的局部变量”。如果编译器在解释空条件运算符时无法期待分配状态,则一种更简单的解决方案是给“无法确定分配状态”之类的警告而不是错误。

4 个答案:

答案 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。不过,这很了不起,因为由于?以及myDictnull的事实,不应调用TryGetValue并留下value “未初始化”

  

空条件运算符正在短路。也就是说,如果条件成员或元素访问操作链中的一个操作返回null,则该链的其余部分将不执行。

     

source

但是... ,因为没有未初始化的变量;如果编译成功,编译器将确保其行为未定义。


因此,由于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,则左侧将返回TryGetValuemyDict本身为空。但是,在一般情况下,编译器无法做出这种推断,因此您必须通过预先测试myDict中的null(并跳过?.)来解决这个问题。或< / strong>预先初始化value

答案 2 :(得分:0)

我建议在您的“ TryGetValue”调用之前初始化value变量。

答案 3 :(得分:0)

空条件运算符引入了一条不同的执行路径,从而使TryGetValue短路。但是,value变量仍然存在,因为它的作用域位于ifTryGetValue语句之外,这表明语法糖的这种风味必须导致变量之前的良好旧声明。使用。例如:

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;

这还会导致编译错误。

但是,如果myDictnull,并且您没有使用空条件运算符,则会对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变量   在方法调用中使用它的地方,您不能在之前不小心使用它   它被分配了。

因此,从理论上讲,由于语言构造允许您在分配变量之前意外地使用它。但是,编译器正在从较低级别识别问题,而不会让您解决。

在我看来,这是语法糖雾掩盖的有效编译器验证。