我经常想知道以下情况是否真的发生在c#
中如果我有一个结构但我没有显式覆盖从对象派生的任何方法,如ToString(),GetHashCode()等,那么如果我声明我的struct类的本地实例并调用'ToString( )'on it,我的struct会被装箱,即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方法指令 已被约束前缀 thisType,执行指令 如下:
如果thisType是引用类型(如 反对值类型)然后ptr是 取消引用并传递为'this' 指向方法的callvirt的指针。
如果thisType是值类型而且 thisType实现方法然后ptr是 通过未经修改的'this' 指向调用方法指令的指针, 用于实现方法 thisType。
如果thisType是值类型而且 thisType没有实现方法 然后ptr被解除引用,装箱,和 作为'this'指针传递给 callvirt方法指令。
那么这是否意味着如果我没有在我的结构类型上显式实现ToString(),那么它将落入最后一个案例并被装箱?或者我在某处误解了它?
答案 0 :(得分:8)
如果
thisType
是值类型而且thisType
没有实现方法 然后ptr被解除引用,装箱,和 作为'this'指针传递给 callvirt方法指令。最后一种情况只有在发生时才会发生 方法是在
Object
上定义的,ValueType
或Enum
并且未被覆盖 按thisType
。在这种情况下,拳击 导致原始对象的副本 要做。
答案是肯定的,值类型是装箱。这就是在自定义结构上覆盖ToString()
总是一件好事。
答案 1 :(得分:7)
修改: kek444's answer是正确的。我为误读这个问题而道歉。我在这里留下答案,因为我相信它为未来的读者提供了额外的价值和相关信息。
我也认为来自reference Mehrdad's answer的引用特别引人深思:
因此,人们不能写一个程序来证明拳击正在发生。通过查看IL并完全理解
- 如果thisType是值类型而且 thisType没有实现方法 然后ptr被解除引用,装箱,和 作为'this'指针传递给 callvirt方法指令。
最后一种情况只有在发生时才会发生 方法是在Object上定义的, ValueType或Enum,不会被覆盖 通过thisType。在这种情况下,拳击 导致原始对象的副本 被制造。但是,因为没有 Object,ValueType和。的方法 枚举修改对象的状态, 无法检测到此事实。
constrained
指令的callvirt
前缀,只能识别它。
来自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)时,通过struct类型的实例调用虚方法不会导致发生装箱。即使将结构体用作类型参数并且通过类型参数类型的实例进行调用,也是如此。例如:
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。
答案 2 :(得分:4)
如果您的结构(why should it? constrained
IL instruction takes care of it)实现了ToString
或GetHashCode
,则不会将其打包。)当您调用非虚方法(或虚方法)时,它会被装箱在System.Object
(其基类)的结构中没有覆盖,即GetType
/ MemberwiseClone
。
更新:对不起可能造成的误解。我写的答案是覆盖结构中的方法(这就是为什么我提到非虚拟方法需要装箱,我应该更明确地不要混淆读者,特别是因为我错过了关于不覆盖方法的陈述),好像你没有覆盖它,Object.ToString
方法期望它的第一个参数(对this
的引用)是一个引用类型(Object
实例)。显然,该值必须在该调用中加入框(因为它是基类中的调用。)
然而,重点是,在值类型上调用虚方法的性质不会导致发出box
指令(与{{1}上的非虚方法不同)总是导致发出明确的Object
指令。)如果必须求助于box
实现(如您在更新的问题中提到的那样),它将执行装箱的callvirt
指令)就像将结构传递给需要Object.ToString
参数的方法一样。