LINQ,输出参数和"使用未分配的局部变量"错误

Jon*_*ood 4 c# linq tryparse

我有一些类似于以下的代码.

class MyClass<TEnum> where TEnum : struct
{
    public IEnumerable<TEnum> Roles { get; protected set; }

    public MyClass()
    {
        IEnumerable<string> roles = ... ;

        TEnum value;
        Roles = from r in roles
                where Enum.TryParse(r, out value)
                select value;   // <---- ERROR HERE!
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,在上面指出的行上,我收到错误:

使用未分配的局部变量'value'

在我看来,value总是会在这种情况下初始化,因为它是一个out参数Enum.TryParse.

这是C#编译器的错误吗?

Eug*_*kal 6

不它不是.

编译器无法保证Enum.TryParse(r, out value)将被执行.

如果roles是空集合怎么办?

即使您在方法中初始化您的集合,CSC也不会考虑roles使用值 - 这是编译器当前无法做到的事情.

如果Enum.TryParse(r, out value)不执行lambda会怎么样- value通过闭包不会得到它的值?

编译器不能给你这样的保证.


您的代码(部分)等效于:

class MyClass<TEnum> where TEnum : struct
{
    public IEnumerable<TEnum> Roles { get; protected set; }

    public MyClass()
    {
        IEnumerable<string> roles = ... ;


        Roles = GetValues();   // <---- ERROR HERE!
    }

    public static IEnumerable<TEnum> GetValues(IEnumerable<String> roles)
    {       
        TEnum value; 
        String[] roleArray = roles.ToArray(); // To avoid the foreach loop.

        // What if roleArray.Length == 0?
        for(int i = 0; i < roleArray.Length; i++)
        {
             // We will never get here
             if (Enum.TryParse(roleArray[i], out value))
                 yield return value;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这段代码对于编译器来说是干净且易懂的(没有错误) - 它知道如果没有执行,Enum.TryParse(roleArray[i], out value)你就不会尝试返回value.


但功能LINQ查询并不是那么简单.

如果我们使用Enumerable扩展重写它,我们将:

 TEnum value;
 Roles =  roles
     .Where(role => Enum.TryParse(role, out value))
     .Select(role => value);   <---- STILL ERROR HERE!
Run Code Online (Sandbox Code Playgroud)

我们再次得到错误.

编译器无法看到value无疑会设置,因为它不了解所用方法的内部 - Where可能或(理论上)可能不会执行lambda,所以如果你添加value变量用于闭包的事实,它就变得非常重要任务是做出这样的保证,没有误报.

  • 那么`value`也不会被评估. (2认同)
  • @JonathanWood - 这不是一个bug,因为编译器根本不知道`Where`会调用`TryParse`.`where`只是一种扩展方法,并不特别. (2认同)
  • 措辞不好,*可能是*,但编译器无法通过"静态流分析"确定该值肯定会被分配. (2认同)

Ben*_*igt 5

TL; DR:错误表示变量(可证明)未分配 - FALSE.现实,变量不能被证明(使用编译器可用的证明定理).


LINQ的设计假设是函数......那些根据输入返回输出并且没有副作用的函数.

一旦重写,它将是:

roles.Where(r => Enum.TryParse(r, out value)).Select(r => value);
Run Code Online (Sandbox Code Playgroud)

并重新改写

Enumerable.Select(Enumerable.Where(roles, r => Enum.TryParse(r, out value)), r => value);
Run Code Online (Sandbox Code Playgroud)

这些LINQ函数将在调用选择lambda之前调用过滤器lambda,但编译器无法知道(至少,没有特殊套管或跨模块数据流分析).更有问题的是,如果Where通过重载决策选择了不同的实现,则可能TryParse不会调用lambda .

编译器的明确赋值规则非常简单,而且在安全方面也是错误的.

这是另一个例子:

bool flag = Blah();
int value;
if (flag) value = 5;
return flag? value: -1;
Run Code Online (Sandbox Code Playgroud)

使用未初始化的值是不可能的,但数据流分析的语言规则会导致编译错误,value而不是"明确分配".

但是,编译器错误的措辞很差.不是"明确分配"与肯定是"未分配"不一样,因为错误声称.