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

时间:2015-03-11 19:04:21

标签: 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!
    }
}

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

  

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

在我看来value始终会被初始化,因为它是out的{​​{1}}参数。

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

2 个答案:

答案 0 :(得分: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;
        }
    }
}

这段代码对于编译器来说是干净且易懂的(没有错误) - 它知道如果不执行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!

我们再次得到错误。

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

答案 1 :(得分:5)

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


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

一旦重写,它将是:

roles.Where(r => Enum.TryParse(r, out value)).Select(r => value);

并重新改写为

Enumerable.Select(Enumerable.Where(roles, r => Enum.TryParse(r, out value)), r => value);

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

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

这是另一个例子:

bool flag = Blah();
int value;
if (flag) value = 5;
return flag? value: -1;

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

然而,编译器错误的措辞很差。不是“明确分配”与肯定是“未分配”不一样,因为错误声称。

相关问题