【问题标题】:LINQ, output arguments, and 'Use of Unassigned Local Variable' errorLINQ、输出参数和“使用未分配的局部变量”错误
【发布时间】:2015-03-11 19:04:20
【问题描述】:

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

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 在这种情况下总是会被初始化,因为它是Enum.TryParseout 参数。

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

【问题讨论】:

  • @SwDevMan81:完全不一样。
  • btw.: 即使它可以编译并完成它的工作 --- 你的代码仍然是错误的。当您选择一个封闭的外部变量时,结果列表的所有成员的值都是相同的。你也可以select 0。最优雅的解决方案是返回 int? 的 TryParse 扩展方法
  • @JonathanWood:实际上,它有很多共同点。在构造 lambda 时而不是在调用它时检查“确定分配”状态这一事实是两者的根本原因。
  • @DasKrümelmonster:你的意思是Parse?但这会在失败时引发异常。他希望每个解析失败的项目为零,每次成功的解析都需要一个项目...这可以使用SelectMany 来完成。

标签: c# linq tryparse


【解决方案1】:

不,不是。

编译器不能保证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,所以如果你添加 @987654332 的事实@ 变量在闭包中使用,在没有误报的情况下做出这样的保证是一项不平凡的任务。

【讨论】:

  • 那么value也不会被评估。
  • "如果角色是一个空集合怎么办?" ,即 +1。
  • @JonathanWood - 这不是一个错误,因为编译器根本不知道Where 会调用TryParseWhere 只是一种扩展方法,并没有什么特别之处。
  • @Habib:所以?这是一个非常不同的情况。
  • 措辞不好,可能是,但是编译器无法确定static flow analysis肯定会分配该值。
【解决方案2】:

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 而没有“明确分配”。

但是,编译器错误的措辞很糟糕。正如错误所声称的那样,未“明确分配”与绝对“未分配”不同。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-04
    • 1970-01-01
    • 2010-11-17
    相关资源
    最近更新 更多