为什么这个(null ||!TryParse)条件导致"使用未分配的局部变量"?

Bra*_*nez 96 c# compiler-construction cil dynamic c#-4.0

以下代码导致使用未分配的局部变量"numberOfGroups":

int numberOfGroups;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}
Run Code Online (Sandbox Code Playgroud)

但是,这段代码工作正常(但ReSharper说这= 10是多余的):

int numberOfGroups = 10;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}
Run Code Online (Sandbox Code Playgroud)

我错过了什么,还是编译器不喜欢我||

我把它缩小到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
}
Run Code Online (Sandbox Code Playgroud)

但是,此代码可以:

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
}
Run Code Online (Sandbox Code Playgroud)

我没有意识到dynamic这将是一个因素.

Eri*_*ert 72

我很确定这是一个编译器错误.很好找!

编辑:这不是一个错误,正如Quartermeister所示; dynamic可能会实现一个奇怪的true运算符,这可能导致y永远不会被初始化.

这是一个最小的repro:

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; 
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为没有理由说这应该是非法的; 如果你用bool替换动态它编译得很好.

我明天正在和C#团队会面; 我会向他们提起这件事.为错误道歉!

  • 我认为编译器行为实际上是正确的,因为`d`的值可能是带有重载的`true`运算符的类型.我已经发布了一个答案,其中一个例子都没有采取分支. (17认同)
  • 我很高兴知道我不会疯狂:)我已经更新了我的代码只依赖于TryParse,所以我现在就开始了.感谢您的见解! (6认同)
  • @NominSim:假设运行时分析失败:然后在读取本地之前抛出异常.假设运行时分析成功:那么*在运行时*d为真且y被设置,或者d为假而M设置为y.无论哪种方式,y都已设定.事实上,分析推迟到运行时不会改变任何东西. (4认同)
  • 如果有人好奇:我刚刚检查过,Mono编译器就是正确的.http://imgur.com/g47oquT (2认同)
  • @Quartermeister在这种情况下Mono编译器错了:) (2认同)

Qua*_*ter 52

如果动态表达式的值是具有重载true运算符的类型,则可以取消分配变量.

||运营商将调用true操作来决定是否评估右侧,然后if语句将调用true操作来决定是否评估其身.对于正常情况bool,这些将始终返回相同的结果,因此将评估一个,但对于用户定义的运算符,没有这样的保证!

在Eric Lippert的复制品的基础上,这是一个简短而完整的程序,它演示了一种情况,即既不会执行路径,也不会有变量的初始值:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这里干得好.我把它传给了C#测试和设计团队; 我明天看到它们时会看到它们是否有任何意见. (8认同)
  • 这对我来说很奇怪.为什么要对'd`进行两次评估?(我并没有质疑它显然*是*,如你所示.)我本来期望`true`的评估结果(来自第一个操作符调用,由`||`引起)被传递"对`if`陈述.例如,如果你在那里放一个函数调用,那肯定会发生什么. (3认同)
  • @DanTao:表达式'd`只被评估一次,正如您所期望的那样.它是被调用两次的`true`运算符,一次是`||',一次是`if`. (3认同)
  • @DanTao:如果我们将它们作为`var cond = d ||放在单独的语句中,可能会更清楚 M(out y); if(cond){...}`.首先,我们评估`d`以获得`EvilBool`对象引用.要评估`||`,我们首先使用该引用调用`EvilBool.true`.返回true,所以我们短路并且不调用`M`,然后将引用赋给`cond`.然后,我们继续讨论`if`语句.`if`语句通过调用`EvilBool.true`来评估其条件. (2认同)
  • 现在这真的很酷.我不知道有真假操作员. (2认同)

Nom*_*Sim 7

来自MSDN(强调我的):

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

在大多数情况下,类型动态类似于类型对象.但是,包含dynamic类型表达式的操作不会被编译器解析或进行类型检查.

由于编译器不键入检查或解析包含dynamic类型表达式的任何操作,因此无法确保通过使用赋值变量TryParse().