动态出现意外的编译时错误

时间:2014-07-23 09:29:08

标签: c# visual-studio-2012 dynamic c#-5.0

  

澄清问题:我不是在寻找如何解决此问题的答案(下面列出了几个),但是为什么正在发生。

我希望编译以下代码:

struct Alice
{
    public string Alpha;
    public string Beta;
}

struct Bob
{
    public long Gamma;
}

static object Foo(dynamic alice)
{
    decimal alpha;
    long beta;

    if (!decimal.TryParse(alice.Alpha, out alpha) // *
        || !long.TryParse(alice.Beta, out beta)) // **
    {
        return alice;
    }

    var bob = new Bob { Gamma = beta }; // ***

    // do some stuff with alice and bob

    return alice;
}

但是// ***

会引发以下编译时错误
  

使用未分配的本地变量'beta'

我可以在以下情况下编译程序:

  1. 如果我将签名更改为

    static object Foo(Alice alice)

  2. 明确地在// *// **行投射,例如:

    !long.TryParse((string)alice.Beta, out beta)

  3. 删除decimal.TryParse上的// *

  4. ||替换短路或| 感谢HansPassant

  5. 交换TryParse s

  6. TryParse s的结果拉入bool s 感谢Chris

  7. 将默认值分配给beta

  8. 我错过了一些明显的东西,或者是否有一些微妙的事情,或者这是一个错误?

10 个答案:

答案 0 :(得分:12)

我不确定答案,但对我而言,它看起来像编译器错误或“按设计”问题。

我用你的样本玩了一下,一点一点地减少它,这就是它留下的东西:

    private static bool Default<T>(out T result)
    {
        result = default(T);
        return true;
    }

    private static void Foo()
    {
        int result;

        if (true || Default(out result))
        {
            return;
        }

        Console.WriteLine(result);
    }

也失败了
  
    

错误CS0165:使用未分配的局部变量'result'

  

您可以在int result中使用Foo来检查您想要的任何类型。

请注意没有dynamic用法,请注意true分支,该分支应立即返回。

对我来说,看起来VS.Net编译器在这里“不够智能”。

这段代码有什么用 - 它可以在.Net 4之前用编译器编译(在适当的框架中使用csc.exe),所以结果如下:

  • .Net 2.0

构建好,警告:

  
    

警告CS0429:检测到无法访问的表达式代码

         

警告CS0162:检测到无法访问的代码

  
  • .Net 3.5

构建失败:

  
    

错误CS0165:使用未分配的局部变量'result'

  

因此,如果 是一个错误,它就会出现在.NET 2和.NET 3.5之间的某个地方

答案 1 :(得分:4)

这是因为dynamic关键字在生成的代码结构中引起了很多变化(由C#编译器生成)。

您可以使用.NET反射器之类的工具来观察这一点(我建议选择&#39; None&#39;进行C#推理,这样您就可以真正看到所有生成的东西)。基本上,每次访问dynamic对象时,生成的代码至少会添加if个案例。这些ifs可能会导致重要的代码路径更改。

例如,这个简单的C#代码

    static void MyFoo(dynamic dyn)
    {
        if (dyn == null)
            return;

        var x = dyn;
    }

生成为:

private static void MyFoo([Dynamic] object dyn)
{
    object obj2;
    CSharpArgumentInfo[] infoArray;
    bool flag;
    if (<MyFoo>o__SiteContainer0.<>p__Site1 != null)
    {
        goto Label_0038;
    }
    <MyFoo>o__SiteContainer0.<>p__Site1 = CallSite<Func<CallSite, object, bool>>.Create(Binder.UnaryOperation(0, 0x53, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(0, null) }));
Label_0038:
    if (<MyFoo>o__SiteContainer0.<>p__Site2 != null)
    {
        goto Label_0088;
    }
    <MyFoo>o__SiteContainer0.<>p__Site2 = CallSite<Func<CallSite, object, object, object>>.Create(Binder.BinaryOperation(0, 13, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(0, null), CSharpArgumentInfo.Create(2, null) }));
Label_0088:
    if ((<MyFoo>o__SiteContainer0.<>p__Site1.Target(<MyFoo>o__SiteContainer0.<>p__Site1, <MyFoo>o__SiteContainer0.<>p__Site2.Target(<MyFoo>o__SiteContainer0.<>p__Site2, dyn, null)) == 0) == null)
    {
        goto Label_00AE;
    }
    obj2 = dyn;
Label_00AE:
    return;
}

让我们举一个简单的例子。这段代码:

    static void MyFoo1(dynamic dyn)
    {
        long value;

        if (long.TryParse(dyn, out value))
            return;

        var x = value;
    }

编译好。这一个

    static void MyFoo2(dynamic dyn)
    {
        long value;

        if (true || long.TryParse(dyn, out value))
            return;

        var x = value;
    }

没有按&#39;吨。如果你查看生成的代码(将值设置为0以确保它编译),它将只包含一些额外的ifsgoto,它们会彻底改变整个代码路径。我不认为这是一个错误,可能更多是dynamic关键字的限制(有很多,例如:Limitations of the dynamic type in C#

答案 2 :(得分:3)

仅适用于||运算符使用(逻辑OR),

编译器从左到右检查条件,因此如果第一个操作数计算为TRUE,则不需要计算第二个操作数,因此Beta可能无法获得值,编译器会抛出以下警告:

"Use of unassigned local variable 'beta'"

您的代码:

    if (
        !decimal.TryParse(alice.Alpha, out alpha)  // If evaluated as TRUE...
        ||                                         // 
        !long.TryParse(alice.Beta, out beta))      // ....so Will not evaluated this.
{

请注意,如果更改操作数顺序,您将收到以下消息:

"Use of unassigned local variable 'alpha'"

答案 3 :(得分:2)

声明

时,将默认值指定为beta和alpha
decimal alpha=default(decimal);
long beta =default(long); 

答案 4 :(得分:2)

嗯,这是我的尝试:

alice类型为&#39; Alice&#39;时,编译器在编译时知道将要调用哪个TryParse方法,并且它知道方法有一个&#39; out&#39;参数,意味着它将由方法初始化。因此,如果alice的类型为Alice,则decimal.TryParse将返回false,在这种情况下,Bob将不会构造(因为该方法返回alice),或{ {1}}将返回true,并且在这种情况下将调用decimal.TryParse,并且编译器知道这将为long.TryParse分配值(因为beta关键字)。< / p>

现在,如果out的类型为alice,则编译器并不关心dynamic方法的签名并推迟方法的解析(基于运行时的实际类型TryParse)。这意味着他无法对alice通过调用beta分配值的事实做出任何假设,因此无法确定TryParse()在使用时是否会被赋值对于Bob的构造函数。

我的2美分......

答案 5 :(得分:1)

看起来我找到了答案。它似乎是一个编译表达式树问题。在编译时,它检查变量是否被赋值。当您对alpha和beta变量的值赋值发生在if条件内时,它假定只评估第一个表达式。

这是一直在评估..但由于它是'或'条件..当第一个表达式为真时,它会从if中断而不评估第二个表达式。

!decimal.TryParse(alice.Alpha,out alpha)

如果你想看到这个,你自己按顺序放置TryParse语句,如下所示。那个错误不会出现..

!decimal.TryParse(alice.Alpha,out alpha) !long.TryParse(alice.Alpha,out beta);

这里保证将某些值分配给beta。我希望我说明问题。

答案 6 :(得分:0)

看起来甚至突出的人都有问题来决定这是一个编译器错误还是设计可以通过查看安东尼D.绿色,程序管理员,Visual Basic&amp ;;的两个答案来看here。 C#语言团队。此错误报告基本上具有与此处描述的相同的问题,即使用可导致CS0165的动态值,而使用其他类型解决它。同时拆分&amp;&amp;对两个if语句的操作改为解决了该bug报告中的问题。

格林先生的答案有一个this question的链接,似乎与此重复。所有这些背后的奥秘在this answer以及格林先生在上面链接的错误报告中得到了很好的解释。 问题是动态类型的值可能有一个由true调用的重载||运算符,if语句和自己的实现可以做任何事情,所以编译器对这些东西很谨慎。链接的信息提供了更好的解释:)

答案 7 :(得分:0)

我认为这种情况正在发生,因为在您当前的代码中,包含dynamic,编译器用于确定在运行时是否实际调用TryParse的算法是不确定的。我认为您所看到的解决方案通过使那些TryParse调用的评估变为确定性(即将对其进行评估)来避免此问题。当使用TryParse参数评估这些out次调用时,他们最多会收到default(decimal)default(long)的值。因此,当您稍后分配时,该值不会被取消分配。这是基于生成的代码中出现的goto跳跃的假设

Int32.TryParse文档

略微支持此功能
  

当此方法返回时,包含等效于s中包含的数字的32位有符号整数值,如果转换成功,则如果转换失败则为零。如果s参数为null或String.Empty,格式不正确,或者表示小于MinValue或大于MaxValue的数字,则转换失败。 此参数未经初始化

这绝对是基于Simon Mourier在答案中的调查结果。

答案 8 :(得分:0)

所以我知道你想知道为什么这种情况正在发生。 就像错误所说的那样,编译器发现了一些可以在不进行初始化的情况下使用变量beta的情况。正如documentation所说:

  

请注意,当编译器遇到可能导致使用未分配变量的构造时会生成此错误,即使您的特定代码不是

如果代码没有在'if'语句中返回,编译器可能不够聪明,无法意识到变量'beta'将始终被初始化。对于编译器来说,这种情况似乎很简单但我怀疑这可能是在编译器中实现的一般特性。

答案 9 :(得分:-1)

long beta;

此语句中实际发生的是此变量的声明,但不是该变量的实例化。执行此语句时,运行时将此变量的内存空间保留为long,但不会将值实例化为任何内容。因此,通过out参数,您尝试将对象分配给未实例化的对象,除了默认值之外,您可以通过简单地实例化对象而不是简单地保留所需的内存来解决此问题

long beta = new long();