LINQ、输出参数和 'Use of Unassigned Local Variable' 错误
LINQ, output arguments, and 'Use of Unassigned Local Variable' error
我有一些类似下面的代码。
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!
}
}
但是,在上面指示的行中,我收到错误:
Use of unassigned local variable 'value'
在我看来 value
在这种情况下将始终被初始化,因为它是 Enum.TryParse
的 out
参数。
这是 C# 编译器的错误吗?
不,不是。
编译器无法保证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)
你就不会尝试 return value
。
但是使用函数式 LINQ 查询就没那么简单了。
如果我们用 Enumerable 扩展重写它,我们将有:
TEnum value;
Roles = roles
.Where(role => Enum.TryParse(role, out value))
.Select(role => value); <---- STILL ERROR HERE!
我们再次收到错误。
编译器看不到 value
将被毫无疑问地设置,因为它不理解所用方法的内部结构 - Where
可能或(理论上)可能不会执行 lambda,所以如果你加上 value
变量在闭包中使用的事实,在没有误报的情况下做出这样的保证成为一项非常重要的任务。
TL;DR:错误表明变量(可证明)未分配——错误。实际上,变量未被证明赋值(使用编译器可用的证明定理)。
LINQ 的设计假设 纯 函数...那些 return 基于输入输出并且没有副作用的函数。
一旦重写,它将是:
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
is used without being "definitely assigned".
但是,编译器错误措辞不佳。不是 "definitely assigned" 与肯定是 "unassigned" 不同,正如错误所指出的那样。
我有一些类似下面的代码。
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!
}
}
但是,在上面指示的行中,我收到错误:
Use of unassigned local variable 'value'
在我看来 value
在这种情况下将始终被初始化,因为它是 Enum.TryParse
的 out
参数。
这是 C# 编译器的错误吗?
不,不是。
编译器无法保证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)
你就不会尝试 return value
。
但是使用函数式 LINQ 查询就没那么简单了。
如果我们用 Enumerable 扩展重写它,我们将有:
TEnum value;
Roles = roles
.Where(role => Enum.TryParse(role, out value))
.Select(role => value); <---- STILL ERROR HERE!
我们再次收到错误。
编译器看不到 value
将被毫无疑问地设置,因为它不理解所用方法的内部结构 - Where
可能或(理论上)可能不会执行 lambda,所以如果你加上 value
变量在闭包中使用的事实,在没有误报的情况下做出这样的保证成为一项非常重要的任务。
TL;DR:错误表明变量(可证明)未分配——错误。实际上,变量未被证明赋值(使用编译器可用的证明定理)。
LINQ 的设计假设 纯 函数...那些 return 基于输入输出并且没有副作用的函数。
一旦重写,它将是:
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
is used without being "definitely assigned".
但是,编译器错误措辞不佳。不是 "definitely assigned" 与肯定是 "unassigned" 不同,正如错误所指出的那样。