【问题标题】:How does object.ToString() work on a boxed value type?object.ToString() 如何处理盒装值类型?
【发布时间】:2017-09-08 16:59:34
【问题描述】:
class Program {

    static void Main(string[] args) {
        Int32 i = 123;
        Double d = 123.456;
        FunPrint(i);
        FunPrint(d);
    }

    static void FunPrint(object obj) {
        Console.WriteLine(obj);
    }
}

我对这个示例的理解是,FunPrint() 首先创建了一个新对象,并根据传递的 ValueType 的值(在本例中为Int32)构建它。其次,调用Object.ToString() 并正确显示特定于ValueType 的字符串格式。

值类型不包含虚函数,所以...

我不明白Object 是如何知道它在内部持有什么类型以便进行正确的字符串格式化。

【问题讨论】:

  • 我想知道这是否是一个模板之类的东西。 Object .. T.ToString() .. 因为编译器会在编译时知道所有类型。但这只是我的猜测。

标签: c# object tostring value-type


【解决方案1】:

调用函数在调用FunPrint之前将参数框起来。

值类型不包含虚函数,所以...

其实他们可以。您可以从值类型实现接口。你不能从一个派生,这限制了覆盖的级别。

但是要虚拟调用一个虚函数函数,你需要把值类型装箱。

该机制在这里同样适用。该值已被装箱,因此您可以调用它的虚拟成员。


编辑以阐明在值类型上调用接口方法:

var i = 123;
i.ToString();                // not boxed, statically resolves to Int32.ToString()

var o = (object)o;           // box
o.ToString();                // virtual call to object.ToString()

var e = (IEquatable<int>)i;  // box
i.Equals(123);               // virtual call to IEquatable<int>.Equals(int)

编辑以包含 Jon Hanna 的建议。在值类型上调用System.Object-虚方法确实需要装箱。

var i = 1234;
i.GetType();    // boxes!

在对应的IL中可以看到:

ldc.i4.s     123
stloc.0      // i
ldloc.0      // i
box          [mscorlib]System.Int32
call         instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()

Object.GetType()不能虚拟调用,有签名:

public extern Type GetType();

但它仍然需要拳击。

【讨论】:

  • 值得一提的是ToString()实际上并没有拆箱一个对象,所以里面的类型是什么并不重要?
  • "要调用虚函数,需要将值类型框起来"...在示例代码中使用的类型的情况下为false。有关详细信息,请参阅stackoverflow.com/questions/18615648/…(显然,所有密封类型都可以进行这种优化)。
  • @CodeCaster 在显示的代码示例中(以object 作为参数)避免装箱为时已晚,实际上Int32.ToString 将作为普通虚拟方法在装箱对象上调用(这有点令人困惑OP 的意思是object.ToString,因为该特定方法永远不会在示例中被调用,因为所有使用的类型都提供了自己的覆盖)。我的评论是关于一般性陈述 - 在某些情况下,虚拟方法调用不需要装箱(或一般的虚拟方法调用),因为在编译/JIT 时知道确切的方法。
  • @cazicss, Int32.ToString() 是虚拟的。这是一个覆盖,它必然是虚拟的。
  • 原因是因为int.ToString() 是一个覆盖,它被定义为对int 的操作,虽然它可以通过基本虚拟定义到达,但也可以通过地址作为值类型方法。相比之下,object.GetType() 仅根据 object 定义,因此无法避免装箱,尽管编译器至少可以优化通常使用 callvirt 的 C# 规则,即使对于非虚拟调用也能获得 null-检查,因为它显然不能为空,所以call 可以安全使用,同时保持 C# 观察行为规则。
【解决方案2】:

值类型不包含虚函数,所以...

真的吗?

struct Foo
{
     public override string ToString() =>
         "Sure looks like a virtual call";

     public override bool Equals(object obj) =>
         "So does this one";
}

所有值类型都继承自object,您可以完全覆盖任何虚方法。您无法进一步扩展它们这一事实是无关紧要的。

【讨论】:

  • 我指的不是一般的结构。我的印象是,像 Int32 这样的原语(它们是值类型)没有虚函数,因为这需要一个 vtable 指针并增加 Int32 的总大小......也许我在这里偏离了基础。
  • 我所说的“值类型”是针对我的问题的。它只有 Int32 和 Double。
  • @cazicss System.Int32 Reference source int 覆盖 ToString()
  • @cazicss C# 没有“原语”的概念。这不是 C# 规范中的术语。 Int32 是一个值类型(它在某些方面是特殊的,但它们在这里都不相关)。它有虚拟方法(ToString 是其中之一). If you have a compile time expression of type int` 并且你在上面调用ToString,编译器足够聪明地知道,即使认为该方法是虚拟的,也只有一种可能的实现,因此可以对该方法进行非虚拟调用。如果将值装箱在object 中,则无法完成,需要进行虚拟调用,并且会发生。
  • 当我假设 Int32 没有虚函数时,调用 obj.ToString() 时如何调用 Int32::ToString()。
【解决方案3】:

对于那些来到这里并同样抨击盒子/拆箱魔法的其他人,我在这里找到了一些关于该主题的可靠进一步阅读:

http://mattwarren.org/2017/08/02/A-look-at-the-internals-of-boxing-in-the-CLR/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-24
    • 2011-09-06
    • 1970-01-01
    • 2019-05-22
    • 1970-01-01
    • 1970-01-01
    • 2015-04-30
    • 1970-01-01
    相关资源
    最近更新 更多