为什么此条件(null ||!TryParse)会导致“使用未分配的局部变量”?


98

以下代码导致使用未分配的局部变量“ 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参数的值用作输入并不足够聪明
Charleh 2013年

3
此处给出的代码未演示所描述的行为;它工作正常。请发布实际演示您所描述的行为的代码,以便我们进行自我编译。给我们整个文件。
埃里克·利珀特

8
啊,现在我们有了一些有趣的东西!
埃里克·利珀特

1
编译器对此感到困惑并不奇怪。动态调用站点的帮助程序代码可能具有某些控制流,这些控制流不能保证分配给该out参数。考虑编译器应该产生什么辅助代码来避免该问题,或者是否有可能,这当然很有趣。
CodesInChaos

1
乍一看,这肯定看起来像是个错误。
埃里克·利珀特

Answers:


73

我很确定这是一个编译器错误。好发现!

编辑:这不是一个漏洞,正如Quartermeister所展示的那样;动态的可能实现一个奇怪的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,则可以正常编译。

我实际上明天要与C#团队见面;我会向他们提及。抱歉的错误!


6
我只是很高兴知道我不会发疯:)从那以后,我就更新了代码,使其仅依赖TryParse,因此现在开​​始。感谢您的见解!
布兰登·马丁内斯

4
@NominSim:假设运行时分析失败:然后在读取本地之前引发异常。假设运行时分析成功:然后在运行时 d为true并设置y,或者d为false且M设置y。无论哪种方式,都设置了y。分析推迟到运行时之前不会改变任何事实。
埃里克·利珀特

2
万一有人好奇:我刚刚检查了一下,Mono编译器就正确了。imgur.com/g47oquT
丹涛

17
我认为编译器的行为实际上是正确的,因为的值d可能是带有重载true运算符的类型。我已经发布了一个答案,并给出了一个示例,其中没有分支。
2013年

2
@Quartermeister,在这种情况下,Mono编译器
会把

52

如果动态表达式的值是带有重载运算符的类型,则可能未分配变量true

||运营商将调用true操作来决定是否评估右侧,然后if语句将调用true操作来决定是否评估其身。对于normal 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();
    }
}

8
辛苦了 我已经将其传递给C#测试和设计团队。明天见,我看看他们是否对此有任何评论。
埃里克·利珀特

3
这对我来说很奇怪。为什么要d进行两次评估?(正如您所显示的,我并不认为它显然。)我希望true(从第一次操作员调用,由by引起的||)评估结果会“传递”给该if语句。例如,如果在其中放置函数调用,肯定会发生这种情况。
丹涛

3
@DanTao:d正如您所期望的,该表达式仅被评估一次。这是true多数民众赞成被调用两次,第一次由运营商||和曾经if
2013年

2
@DanTao:如果将它们放在单独的语句中,可能会更清楚var cond = d || M(out y); if (cond) { ... }。首先,我们评估d以获得EvilBool对象引用。要评估||,我们首先EvilBool.true使用该引用进行调用。这返回true,所以我们短路了并且不调用M,然后将引用分配给cond。然后,我们继续if陈述。该if语句通过调用评估其条件EvilBool.true
2013年

2
现在,这真的很酷。我不知道运算符是对还是错。
IllidanS4希望Monica

7

从MSDN(重点是我的):

动态类型使它发生的操作能够绕过编译时类型检查。而是,这些操作在运行时解决。动态类型简化了对COM API(例如Office Automation API)以及动态API(例如IronPython库)和HTML文档对象模型(DOM)的访问。

在大多数情况下,动态类型的行为类似于类型对象。但是,编译器无法解析或检查包含动态类型的表达式的操作。

由于编译器不进行类型检查或解析包含动态类型的表达式的任何操作,因此它不能确保通过使用来分配变量TryParse()


如果满足第一个条件,numberGroups则进行分配(在if true块中),如果不满足,则第二个条件保证进行分配(通过out)。
leppie

1
这是一个有趣的想法,但是代码无需myString == null(仅依赖TryParse)就可以正常编译。
布兰登·马丁内斯

1
@leppie的要点是,由于第一个条件(实际上是整个if表达式)都涉及一个dynamic变量,因此它在编译时不会解析(因此,编译器无法进行这些假设)。
NominSim 2013年

@NominSim:我明白你的意思了:) +1可能是编译器的一项牺牲(违反C#规则),但是其他建议似乎暗示了一个错误。埃里克(Eric)的片段显示这不是一个牺牲,而是一个错误。
leppie

@NominSim这是不对的;仅仅因为某些编译器函数被推迟并不意味着它们全部都被推迟。有大量证据表明,在稍微不同的情况下,尽管存在动态表达式,但编译器仍可以毫无问题地进行定值分配分析。
dlev
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.