【问题标题】:Anomaly when using 'var' and 'dynamic'使用 'var' 和 'dynamic' 时的异常
【发布时间】:2011-09-16 02:49:21
【问题描述】:

我第一次在 Anomaly 上遇到了一些问题,使用 var 关键字 bit me。

采取这个非常简单的方法

public static Int32? GetNullableInt32(Int32 num)
{
    return new Nullable<Int32>(num);
}

现在我们可以使用dynamic 参数调用此方法,一切都会按预期进行。

public static void WorksAsAdvertised()
{
    dynamic thisIsAnInt32 = 42;

    //Explicitly defined type (no problems)
    Int32? shouldBeNullableInt32 = GetNullableInt32(thisIsAnInt32);

    Console.Write(shouldBeNullableInt32.HasValue);
}

但是,通过使用隐式类型声明 shouldBeNullableInt32,结果与 的预期相差甚远。

public static void BlowsUpAtRuntime()
{
    dynamic thisIsAnInt32 = 42;

    //Now I'm a dynamic{int}... WTF!!!
    var shouldBeNullableInt32 = GetNullableInt32(thisIsAnInt32);

    //Throws a RuntimeBinderException
    Console.Write(shouldBeNullableInt32.HasValue);
}

返回值不是Nullable&lt;Int32&gt;,而是被视为动态类型。即便如此,底层的Nullable&lt;T&gt; 也不会被保留。由于System.Int32 没有名为HasValue 的属性,因此会抛出RuntimeBinderException

我会非常很想听到有人能真正解释正在发生的事情(而不仅仅是猜测)。

两个问题

  1. GetNullableInt32 的返回类型清楚地返回Nullable&lt;Int32&gt; 时,为什么shouldBeNullableInt32被隐式键入为动态
  2. 为什么底层Nullable&lt;Int32&gt;没有被保留?为什么要用dynamic{int} 代替? 在这里回答C# 4: Dynamic and Nullable<>

更新

Rick Sladkey's answerEric Lippert's answer 都同样有效。请同时阅读它们:)

【问题讨论】:

  • 此外,如果您将dynamic thisIsAnInt32 = 42; 更改为int thisInAnInt32 = 42,问题也会消失。
  • 可能与这个问题有关:stackoverflow.com/questions/3728752/c-4-dynamic-and-nullable。至少它解决了为什么不保留底层 Nullable 的原因。
  • @shf301 - 对。我没有在上面的例子中包括那个,因为我觉得它是给定的。删除动态关键字使所有内容都非常明确。我真正想知道的是,为什么使用动态作为方法的参数会导致编译器覆盖看起来应该是无需动脑筋的东西。但是,我对编译器理论知之甚少,对我来说似乎很基础的东西实际上可能是一个非常复杂的问题。
  • 看到IL的代码,方法的返回类型改为valueType而不是int32?类型。因此,当您明确指定持有类型时,即 shouldBeNullableInt32 并且因为 int32 是 valuetype 的后代。因此,事情进展顺利。但是当你说 var 时,这里的事情并不明确。我猜返回类型仍然是值类型。但令我惊讶的是,如果你对它执行 GetType,你会得到 int32 n 而不是 valuetype。但是,嘿,var item 上的 hasvalue 应该给你例外,对吧?

标签: c# dynamic compiler-construction


【解决方案1】:
  1. GetNullableInt32 的返回类型清楚地返回Nullable&lt;Int32&gt; 时,为什么shouldBeNullableInt32被隐式类型化为动态

这是因为虽然GetNullableInt32 是要被调用的方法对我们来说很明显,但由于dynamic binding确实 被调用的实际方法被推迟到运行-time,因为它是用动态参数调用的。 GetNullableInt32 的另一个重载可能与 thisIsAnInt32 的运行时值匹配得更好。该替代方法在运行时才能知道,可能会返回Int32?以外的其他类型

因此,由于 动态绑定 而不是静态绑定,编译器无法在编译时假定表达式的返回类型是什么,因此表达式返回类型 dynamic。这可以通过将鼠标悬停在 var 上来查看。

您似乎已经对这里的第二个问题做出了令人满意的解释:

【讨论】:

  • 我并没有真正想到这一点,但它确实很有道理。
  • 搞定逻辑让我头疼,但它确实有道理。
【解决方案2】:

Rick 的回答很好,但总结一下,您遇到了该功能的两个基本设计原则的后果:

  1. 如果您要求动态绑定,那么您将获得动态绑定
  2. 动态只是戴着滑稽帽子的对象

您发现的第一个问题是第一设计原则的结果。您要求将调用分析推迟到运行时。编译器这样做了。这包括将调用的一切推迟到运行时,包括重载决议和确定返回类型。编译器有足够的信息来猜测您的意思是无关紧要的。

如果编译器确实猜到了你的意思,那么现在你会问一个不同的问题,即“我对可用的方法集做了一个微小的改变,突然编译器改变了它的推导动态的类型,为什么?”当编译器的行为不可预测时,用户会非常困惑。

(话虽如此,编译器会在少数情况下告诉您动态代码是错误的。在某些情况下,我们知道动态绑定将总是在运行时失败,我们可以在编译时告诉你它们,而不是等待你的测试用例失败。)

您发现的第二个问题是第二个设计原则的结果。因为动态只是一个戴着滑稽帽子的对象,并且因为 nullables 框到一个 null 引用或一个装箱的不可为 null 的值类型,所以没有所谓的“动态 nullable”。

【讨论】:

  • 我真的希望我可以将两个答案标记为已接受。或者合并它们,并给你两个信用。尽管我是 Lippert 的忠实粉丝,但我决定将 @Rick 的回答标记为已接受,因为他是第一个。不是你可能关心,但你知道......
猜你喜欢
  • 2014-09-12
  • 2019-03-29
  • 1970-01-01
  • 1970-01-01
  • 2022-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-21
相关资源
最近更新 更多