【问题标题】:boxing on structs when calling ToString()调用 ToString() 时对结构进行装箱
【发布时间】:2009-08-08 14:55:22
【问题描述】:

我经常想知道下面的场景是否真的发生在c#中

如果我有一个结构,但我没有显式覆盖任何从对象派生的方法,例如 ToString()、GetHashCode() 等,那么如果我声明一个结构类的本地实例并调用 'ToString( )' 上,我的结构会被装箱吗,即 CLR 是否会将其隐式转换为堆上的对象,然后调用 ToString()?还是足够聪明地知道该结构没有实现并忽略它?

public struct Vector2D
{
    public float m_x;
    public float m_y;


    ...... etc
}


void SomeFunc()
{
  Vector2D aVec = new Vector2D();
  Console.WriteLine(aVec.ToString()); // <-- does aVec get boxed here?
  ..... 
}

== 编辑 - 更新== Mehrdad 的link to MSDN,虽然很有用,但让我有点困惑。 我会引用,看看是否有人可以为我解开这个

当一个 callvirt 方法指令 以 constrained 为前缀 thisType,指令被执行 如下:

如果 thisType 是引用类型(如 与值类型相反)然后 ptr 是 取消引用并作为“this”传递 指向方法的 callvirt 的指针。

如果 thisType 是一个值类型并且 thisType 实现方法然后 ptr 是 通过未修改的“this” 指向调用方法指令的指针, 对于方法的实现 这个类型。

如果 thisType 是一个值类型并且 thisType 没有实现方法 然后 ptr 被取消引用,装箱,并且 作为“this”指针传递给 callvirt 方法说明。

那么这是否意味着如果我没有在我的结构类型上显式实现 ToString() 它将落入最后一种情况并被装箱?还是我在某个地方误解了它?

【问题讨论】:

标签: c# performance


【解决方案1】:

如果thisType 是一个值类型并且 thisType 没有实现方法 然后 ptr 被取消引用,装箱,并且 作为“this”指针传递给 callvirt 方法说明。

最后一种情况只有在以下情况下才会发生 方法在Object 上定义, ValueTypeEnum 且未被覆盖 通过thisType。在这种情况下,拳击 导致原始对象的副本 制作。

答案是肯定的,值类型是boxed。这就是为什么在自定义结构上覆盖 ToString() 总是一件好事。

【讨论】:

  • @zebrabox:除非你没有在对象上调用ToString()。 :)
【解决方案2】:

编辑: kek444's answer 是正确的。我很抱歉误读了这个问题。我在这里留下我的答案,因为我相信它对未来的读者有额外的价值和相关信息。

我也认为Mehrdad's answerreference的这句话特别发人深省:

  • 如果 thisType 是值类型并且 thisType 没有实现方法 然后 ptr 被取消引用,装箱,并且 作为“this”指针传递给 callvirt 方法说明。

最后一种情况只有在以下情况下才会发生 方法是在对象上定义的, ValueType 或 Enum 且未被覆盖 按此类型。在这种情况下,拳击 导致原始对象的副本 被制造。然而,由于没有一个 Object、ValueType 和 枚举修改对象的状态, 无法检测到这一事实。

因此,不能编写程序来证明拳击正在发生。只有通过查看 IL 并完全理解 callvirt 指令的 constrained 前缀才能辨别。


来自http://download.microsoft.com/download/3/8/8/388e7205-bc10-4226-b2a8-75351c669b09/CSharp%20Language%20Specification.doc (http://msdn.microsoft.com/en-us/vcsharp/aa336809.aspx) 的 C# 语言规范的第 11.3.5 节:

当结构类型覆盖从 System.Object 继承的虚拟方法(例如 Equals、GetHashCode 或 ToString)时,通过结构类型的实例调用虚拟方法不会导致装箱发生。即使将结构用作类型参数并且通过类型参数类型的实例进行调用也是如此。例如:

using System;
struct Counter
{
    int value;
    public override string ToString() {
        value++;
        return value.ToString();
    }
}
class Program
{
    static void Test<T>() where T: new() {
        T x = new T();
        Console.WriteLine(x.ToString());
        Console.WriteLine(x.ToString());
        Console.WriteLine(x.ToString());
    }
    static void Main() {
        Test<Counter>();
    }
}

程序的输出是:

1
2
3

虽然 ToString 有副作用是不好的风格,但该示例表明 x.ToString() 的三个调用没有发生装箱。

同样,在访问受约束类型参数的成员时,也不会隐式发生装箱。例如,假设接口 ICounter 包含可用于修改值的方法 Increment。如果将 ICounter 用作约束,则调用 Increment 方法的实现时会引用调用 Increment 的变量,而不是装箱副本。

using System;
interface ICounter
{
    void Increment();
}
struct Counter: ICounter
{
    int value;
    public override string ToString() {
        return value.ToString();
    }
    void ICounter.Increment() {
        value++;
    }
}
class Program
{
    static void Test<T>() where T: ICounter, new() {
        T x = new T();
        Console.WriteLine(x);
        x.Increment();                      // Modify x
        Console.WriteLine(x);
        ((ICounter)x).Increment();      // Modify boxed copy of x
        Console.WriteLine(x);
    }
    static void Main() {
        Test<Counter>();
    }
}

第一次调用 Increment 修改了变量 x 中的值。这不等同于对 Increment 的第二次调用,后者会修改 x 的装箱副本中的值。因此,程序的输出是:

0
1
1

有关装箱和拆箱的更多详细信息,请参阅 §4.3。

【讨论】:

  • 感谢您的回答,但我的问题实际上是如果您不覆盖 toString() 会发生什么
  • 是的,关于必须了解 IL 的好处。它没有显式发出一个盒子指令,所以“盒子”是通过callvirt实例字符串[mscorlib]System.Object::ToString()调用基类实现的结果发生的
【解决方案3】:

不,当您调用 ToStringGetHashCode 如果它由您的结构(why should it? constrained IL instruction takes care of it)实现时,它不会被装箱。当您调用非虚拟方法(或结构中未覆盖的虚拟方法)时,它会被装箱在System.Object(它的基类)上,即GetType/MemberwiseClone

更新:对于可能造成的误解,我们深表歉意。我写的答案是考虑到重写结构中的方法(这就是为什么我提到非虚拟方法需要装箱,我应该更明确地不混淆读者,特别是因为我错过了你关于不重写方法的声明)好像你不要覆盖它,Object.ToString 方法期望它的第一个参数(对this 的引用)是一个引用类型(一个Object 实例)。显然,该值必须在该调用中装箱(因为它是基类中的调用。)

然而,关键是,在值类型上调用虚拟方法的性质不会导致发出 box 指令(与 Object 上的非虚拟方法总是导致发出明确的box 指令。)如果必须求助于Object.ToString 实现(正如您在更新的问题中提到的),就像您将结构传递给需要 object 参数的方法。

【讨论】:

  • 重点是——它会被装箱吗?与实际的 Box 操作性能相比,谁/什么启动了 Box 操作的问题是微不足道的,这就是这里的问题。
  • kek444:谢谢指出;固定的。我错过了 OP 的主要观点,并认为“...等”是方法实现。
  • kek444:是的。你的答案是正确的。实际上,当且仅当方法实现在System.Object 中时,值类型才会被装箱。但是,虚拟和非虚拟方法在 IL 级别的行为完全不同。我认为这个问题比我最初想象的要简单得多。
  • 没问题。我认为有几个人误读了我的问题,这意味着我可能可以更好地表述它
  • zebrabox:不。这完全是我的错。您明确提到不要覆盖它。这个问题的简单答案是kek444。我会接受那个。但是,我会将这个答案留给可能对实现细节感兴趣的未来读者。
猜你喜欢
  • 2011-03-03
  • 2014-12-23
  • 1970-01-01
  • 2011-07-04
  • 1970-01-01
  • 2015-05-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多