代码之家  ›  专栏  ›  技术社区  ›  zebrabox

调用ToString()时对结构进行装箱

  •  11
  • zebrabox  · 技术社区  · 15 年前

    我经常想知道下面的场景是否真的发生在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方法指令 已由约束前缀 此类型,执行指令 如下:

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

    如果此类型是值类型并且 此类型实现方法,然后ptr为 传递时未修改为“this” 指向调用方法指令的指针, 方法的实施 蓟

    如果此类型是值类型并且 此类型不实现方法 然后取消引用、装箱和 作为“this”指针传递到 callvirt方法指令。

    这是否意味着如果我不在我的结构类型上显式地实现toString(),它将落入最后一个案例并装箱?或者我在什么地方弄错了?

    3 回复  |  直到 15 年前
        1
  •  8
  •   Fabrício Matté    15 年前

    如果 thisType 是值类型,并且 蓟马 不实现方法 然后取消引用、装箱和 作为“this”指针传递到 callvirt方法指令。

    最后一种情况只有在 方法定义于 Object , ValueType Enum 不被覆盖 通过 蓟马 . 在这种情况下,拳击 导致原始对象的副本 被制造。

    答案是“是”,值类型是 装箱的 . 这就是为什么总是一件好事 ToString() 在自定义结构上。

        2
  •  7
  •   Community Egal    7 年前

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

    我也认为这句话出自 reference 在里面 Mehrdad's answer 特别值得思考的是:

    • 如果此类型是值类型并且 此类型不实现方法 然后取消引用、装箱和 作为“this”指针传递到 callvirt方法指令。

    最后一种情况只有在 方法是在对象上定义的, 值类型或枚举,但不重写 用它来形容。在这种情况下,拳击 导致原始对象的副本 被制造。但是,因为没有 对象、值类型和的方法 枚举修改对象的状态, 无法检测到这一事实。

    因此,人们不能编写一个程序来证明拳击正在进行。只有通过观察IL并完全理解 constrained 的前缀 callvirt 指令。


    根据C语言规范第11.3.5节 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 ):

    当结构类型重写从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包含可用于修改值的方法增量。如果将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节。

        3
  •  4
  •   Mehrdad Afshari    15 年前

    不,你打电话的时候它没有盒装 ToString GetHashCode 如果它是由您的结构实现的( why should it? constrained IL instruction takes care of it )当您在上调用非虚方法(或结构中未重写的虚方法)时,它被装箱。 System.Object (其基本类),即 GetType / MemberwiseClone .

    更新: 抱歉,这可能造成误解。我写的答案是重写结构中的方法(这就是我提到的非虚拟方法需要装箱的原因,我应该更明确一些,不要混淆读者,特别是因为我错过了您关于不重写方法的声明),就好像您不重写它一样, Object.ToString 方法 期待 这是第一个论点(参考 this )作为参考类型 Object 实例)。显然,该值必须在该调用中装箱(因为它是基类中的调用)。

    然而,关键是,对值类型调用虚方法的性质 导致发出 box 指令(与上的非虚拟方法不同 对象 总是会导致 说明。)这是 callvirt 如果必须使用 对象字符串 实现(如您在更新的问题中所提到的)就像将结构传递给需要 object 参数。