为啥这个 (null || !TryParse) 条件会导致“使用未分配的局部变量”?
Posted
技术标签:
【中文标题】为啥这个 (null || !TryParse) 条件会导致“使用未分配的局部变量”?【英文标题】:Why does this (null || !TryParse) conditional result in "use of unassigned local variable"?为什么这个 (null || !TryParse) 条件会导致“使用未分配的局部变量”? 【发布时间】:2013-04-24 16:36:12 【问题描述】:以下代码导致使用未分配的局部变量“numberOfGroups”:
int numberOfGroups;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
numberOfGroups = 10;
但是,这段代码可以正常工作(尽管ReSharper 说= 10
是多余的):
int numberOfGroups = 10;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
numberOfGroups = 10;
是我遗漏了什么,还是编译器不喜欢我的||
?
我已将其缩小到导致问题的dynamic
(options
是我上面代码中的一个动态变量)。问题仍然存在,为什么我不能这样做?
这段代码不能编译:
internal class Program
#region Static Methods
private static void Main(string[] args)
dynamic myString = args[0];
int myInt;
if(myString == null || !int.TryParse(myString, out myInt))
myInt = 10;
Console.WriteLine(myInt);
#endregion
但是,这段代码确实:
internal class Program
#region Static Methods
private static void Main(string[] args)
var myString = args[0]; // var would be string
int myInt;
if(myString == null || !int.TryParse(myString, out myInt))
myInt = 10;
Console.WriteLine(myInt);
#endregion
我没有意识到 dynamic
会是其中的一个因素。
【问题讨论】:
不要认为知道你没有使用传递给out
参数的值作为输入足够聪明
这里给出的代码没有展示所描述的行为;它工作得很好。请发布实际演示您所描述的行为我们可以自己编译的代码。给我们整个文件。
啊,现在我们有了一些有趣的东西!
编译器对此感到困惑并不奇怪。动态调用站点的帮助程序代码可能有一些不能保证分配给out
参数的控制流。考虑编译器应该生成什么帮助代码来避免这个问题,或者这是否可能,这当然很有趣。
乍一看,这确实像一个错误。
【参考方案1】:
From MSDN(强调我的):
动态类型允许其发生的操作绕过编译时类型检查。相反,这些操作在运行时解决。动态类型简化了对 COM API(例如 Office Automation API)的访问,也简化了对动态 API(例如 IronPython 库)和 html 文档对象模型 (DOM) 的访问。
在大多数情况下,动态类型的行为类似于类型对象。但是,包含动态类型表达式的操作不会被编译器解析或类型检查。
由于编译器不进行类型检查或解析任何包含动态类型表达式的操作,因此它无法确保将通过使用TryParse()
来分配变量。
【讨论】:
如果满足第一个条件,则分配numberGroups
(在if true
块中),如果不满足,则第二个条件保证分配(通过out
)。
这是一个有趣的想法,但是代码在没有myString == null
(仅依赖TryParse
)的情况下编译得很好。
@leppie 关键是因为第一个条件(实际上是整个if
表达式)涉及一个dynamic
变量,它在编译时没有被解析(因此编译器无法生成那些假设)。
@NominSim:我明白你的意思:) +1 可能是编译器的牺牲品(违反 C# 规则),但其他建议似乎暗示了一个错误。 Eric 的 sn-p 表明这不是牺牲,而是错误。
@NominSim 这不可能;仅仅因为某些编译器函数被延迟并不意味着它们都是。有大量证据表明,在稍有不同的情况下,编译器可以毫无问题地进行明确的赋值分析,尽管存在动态表达式。【参考方案2】:
我很确定这是一个编译器错误。很好的发现!
编辑:这不是错误,正如 Quartermeister 所展示的那样; dynamic 可能会实现一个奇怪的 true
运算符,这可能会导致 y
永远不会被初始化。
这是一个最小的复制:
class Program
static bool M(out int x)
x = 123;
return true;
static int N(dynamic d)
int y;
if(d || M(out y))
y = 10;
return y;
我看不出这应该是非法的;如果你用 bool 替换 dynamic 它编译就好了。
实际上我明天要与 C# 团队会面;我会跟他们提的。为错误道歉!
【讨论】:
我很高兴知道我不会发疯 :) 我已经更新了我的代码以仅依赖 TryParse,所以我现在就准备好了。感谢您的洞察力! @NominSim:假设运行时分析失败:那么在读取本地之前抛出异常。假设运行时分析成功:那么在运行时要么d 为真且设置y,要么d 为假且M 设置y。无论哪种方式,都设置了 y。分析被推迟到运行时这一事实并没有改变任何事情。 万一有人好奇:我刚刚检查过,Mono 编译器做对了。 imgur.com/g47oquT 我认为编译器的行为实际上是正确的,因为d
的值可能是具有重载true
运算符的类型。我已经发布了一个答案,其中没有一个分支。
@Quartermeister 在这种情况下 Mono 编译器出错了 :)【参考方案3】:
如果动态表达式的值属于具有重载的true
运算符的类型,则可能会取消分配变量。
||
操作符将调用true
操作符来决定是否计算右侧,然后if
语句将调用true
操作符来决定是否计算其主体。对于普通的bool
,这些将始终返回相同的结果,因此将准确评估一个,但对于用户定义的运算符,没有这样的保证!
建立在 Eric Lippert 的 repro 之上,这里有一个简短而完整的程序,它演示了两个路径都不会被执行并且变量将具有其初始值的情况:
using System;
class Program
static bool M(out int x)
x = 123;
return true;
static int N(dynamic d)
int y = 3;
if (d || M(out y))
y = 10;
return y;
static void Main(string[] args)
var result = N(new EvilBool());
// Prints 3!
Console.WriteLine(result);
class EvilBool
private bool value;
public static bool operator true(EvilBool b)
// Return true the first time this is called
// and false the second time
b.value = !b.value;
return b.value;
public static bool operator false(EvilBool b)
throw new NotImplementedException();
【讨论】:
这里做得很好。我已将其传递给 C# 测试和设计团队;明天我看到他们时,我会看看他们是否有任何cmets。 这对我来说很奇怪。为什么要对d
进行两次评估? (正如您所展示的那样,我并不争辩它显然 是。)我本来希望 true
的评估结果(来自第一个运算符调用,由 ||
引起)被“传递”给if
声明。例如,如果你在其中放置一个函数调用,肯定会发生这种情况。
@DanTao:正如您所料,表达式d
只计算一次。 true
运算符被调用了两次,一次由||
调用,一次由if
调用。
@DanTao:如果我们将它们放在单独的语句中,例如var cond = d || M(out y); if (cond) ...
,可能会更清楚。首先我们评估d
以获得EvilBool
对象引用。为了评估||
,我们首先使用该引用调用EvilBool.true
。这返回真,所以我们短路并且不调用M
,然后将引用分配给cond
。然后,我们转到if
语句。 if
语句通过调用 EvilBool.true
来评估其条件。
现在这真的很酷。我不知道有真假运算符。以上是关于为啥这个 (null || !TryParse) 条件会导致“使用未分配的局部变量”?的主要内容,如果未能解决你的问题,请参考以下文章
如果 Int32.TryParse 无法解析字符串,如何将其输出设置为 null? [复制]
int.TryParse & Convert.ToInt32