我有一些类似于以下的代码。
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#编译器的错误吗?
答案 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
被使用而不是“明确分配”。
然而,编译器错误的措辞很差。不是“明确分配”与肯定是“未分配”不一样,因为错误声称。