如何将x.ToString()传递给期望对象类型的方法,而不仅仅是x阻止拳击?

Xai*_*oft 5 c#

我有一个调用的方法OutputToScreen(object o),它被定义为:

public void OutputToScreen(object o)
{
    Console.WriteLine(o.ToString());
}
Run Code Online (Sandbox Code Playgroud)

在我的主要调用方法中,如果我执行以下操作:

int x = 42;
OutputToScreen(x); // x will be boxed into an object
Run Code Online (Sandbox Code Playgroud)

但是,如果我这样做,

OutputToScreen(x.ToString()); // x is not boxed
Run Code Online (Sandbox Code Playgroud)

我仍然不确定为什么x在第二种方法中没有装箱,我只是在来自quickcert的免费视频中看到它.有人可以给出好的解释吗?

以下是基于评论的其他问题:

如果我传入x.ToString(),它类似于:

string temp = x.ToString(); 然后传递temp,当我将x框格式化为字符串类型时,仍然不会发生装箱

Eri*_*ert 13

到目前为止,没有一个答案或评论对问题的相关部分给出准确或密切的解释.很明显,当字符串传递给OutputToScreen时没有装箱.相关问题是当通过调用ToString(对象上的虚方法)生成字符串时没有装箱的原因.

首先,让我们考虑问题的前提.你的问题是由你通过避免拳击获得的假设推动的.也就是说,要么(1)你框,然后调用ToString,然后显示结果,或者(2)你跳过装箱,调用ToString,并显示结果.虽然这听起来像是一场胜利,但你必须在背景下考虑胜利.与拳击相比,转换为字符串并显示到屏幕都很慢; 试图通过避免拳击使程序更快,就像试图通过跑到邮局而不是走路来更快地支付您的有线电视费用.

最终,任何性能问题都应通过双向尝试并在与用户相关的性能指标的上下文中测量效果来解决.

然而,即使避免拳击的效果与性能原因无关,该问题仍然是普遍感兴趣的.

您的问题似乎是基于对拳击工作原理的错误理解:

当我将x格式化为字符串类型时,仍然没有发生装箱?

没有拳击"到字符串类型"这样的东西.int可以装入盒装int.这一切是一个int可以框来.双层盒装盒装双人装.等等.

你应该问的真正感兴趣的问题是:

为什么调用ToString(),一个在对象上声明的虚方法,而不是int int to object,以便将一个对象作为"this"传递给调用?

答案很简单.当struct提供虚拟方法的可访问实现,并在结构上调用该虚方法时,C#编译器知道虚方法在其他任何地方都没有被覆盖,因为结构都是密封的.由于编译器确切知道正在调用什么虚拟方法,以及该方法的"this"引用究竟是什么,因此它可以生成直接调用该方法的代码,无需装箱,随后在vtable中查找该方法.

int确实提供了ToString 的公共覆盖,因此在int上调用ToString不会对int进行封装,在boxed int的vtable中查找ToString,然后在盒装int上调用int的ToString.编译器跳过中间人并直接进入呼叫.

现在,假设该方法不是虚拟的.GetType()不是虚拟的.当你在int上调用GetType()时,编译器不能这样理由.它知道GetType()只有一个实现,它在System.Object上,并且它需要一个对象.所以它是盒子.

现在您已经了解了所有这些,您可以理解这个疯狂的程序片段的行为:

int? x = null;
Console.WriteLine(x.ToString());
Console.WriteLine(x.GetType());
Run Code Online (Sandbox Code Playgroud)

这是做什么的,为什么?

诠释?覆盖ToString,因此对ToString的调用成功.但是GetType不是虚拟的,因此x被加框.没有盒装的可空值类型; null int?框到空引用.因此,这会调用null.GetType()并抛出空引用异常.拳击可以隐藏在你不期望的地方!

如果你构成自己的结构:

struct S { /* does not override ToString */ }
Run Code Online (Sandbox Code Playgroud)

然后打电话给

S s = new S();
string str = s.ToString();
Run Code Online (Sandbox Code Playgroud)

将是框,因为只有对象版本可用,并且将对象作为"this".

巧合的是,你的问题的一个变体是我的博客上周一的主题.有关详细信息,请参阅该文章.

http://blogs.msdn.com/b/ericlippert/archive/2011/03/14/to-box-or-not-to-box-that-is-the-question.aspx

什么是vtable?

vtable是在运行时查找虚拟方法的机制.请参阅昨天的答案,了解vtable的工作原理:

虚函数C#

  • 回复:步行与跑步:你的比喻赢得了互联网.照常. (5认同)

RQD*_*QDQ 5

UPDATE

在查看"似是而非"这个词的意思之后,我做了一些挖掘.该网站提供了一些优秀的IL级细分:

http://weblogs.asp.net/ngur/archive/2003/12/16/43856.aspx

确实,在x.ToString()的情况下没有拳击,但原因似乎更加微妙.

显然,在结构(也就是值类型)的情况下,对ToString()的调用可能或可能在运行时进行装箱.如果结构覆盖ToString的实现,则调用ToString()不被视为装箱.

在Int32的特定情况下,确实覆盖了该方法:

public override string ToString()
{
   return Number.FormatInt32(this, null, NumberFormatInfo.CurrentInfo);
}
Run Code Online (Sandbox Code Playgroud)

在第一种情况下,整数被视为引用类型(在这种情况下,通过将其存储在引用类型变量中):

int x = 42;
object boxed = x;
OutputToScreen(boxed);
Run Code Online (Sandbox Code Playgroud)

在第二种情况下,您实际上并没有将x传递给OutputToScreen.您将x.ToString()的结果传递给OutputToScreen,它已经是一个引用类型(即它已经是一个对象).此外,涉及的结构(在本例中为Int32)已重写ToString,因此对ToString()的调用不被视为装箱.此方法的返回类型已经是引用类型,因此将temp传递给OutputToScreen也不是装箱.

int x = 42;
string temp = x.ToString();
OutputToScreen(temp);
Run Code Online (Sandbox Code Playgroud)

  • @Xaisoft:只要在表达式的类型必须是引用类型的上下文中使用值类型的值,就会发生Boxing.当int存储在object类型的变量中时,无论是局部变量,字段还是调用的形式参数,都必须将其装箱.但拳击不仅发生在一个值存储在引用类型的变量中; 只要值*被用作*作为参考类型,它就会发生.例如,当用作GetType()调用的接收者,或者当转换为IDisposable时,等等. (3认同)
  • @Joan:一个可以为null的值类型只不过是一个包含(T,bool)对的结构,其中bool的意思是"它不是空的吗?".也就是说,"**struct**Nullable <T> {bool HasValue; T t;}"和相应的访问器等.可以为空的int只是一种传递int的方便方式,加上一个标志,指示是否int为null或不为null.其他一切只是编译器和运行时技巧.盒装值类型基本上是"**class**Box <T> {T t;}",其中类还实现了T实现的接口,可以转换回T,依此类推. (2认同)
  • @Joan:如果一个类型在逻辑上是一个*值*那么一个可空类型在逻辑上也是一个*值*.将"decimal"作为值类型,并且*具有*每个十进制值加上null*的类型是引用类型,这似乎很奇怪. (2认同)