【问题标题】: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;
}

是我遗漏了什么,还是编译器不喜欢我的||

我已将其缩小到导致问题的dynamicoptions 是我上面代码中的一个动态变量)。问题仍然存在,为什么我不能这样做

这段代码不能编译:

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 参数的控制流。考虑编译器应该生成什么帮助代码来避免这个问题,或者这是否可能,这当然很有趣。
  • 乍一看,这确实像一个错误。

标签: c# c#-4.0 dynamic compiler-construction cil


【解决方案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 来评估其条件。
  • 现在这真的很酷。我不知道有真假运算符。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-19
  • 2013-01-06
  • 2011-09-06
  • 2012-08-30
  • 1970-01-01
  • 2012-03-03
相关资源
最近更新 更多