我经常想知道c#中是否真的发生了以下情况
如果我有一个结构但我没有显式覆盖从对象派生的任何方法,如ToString(),GetHashCode()等,那么如果我声明我的struct类的本地实例并调用'ToString()'它,我的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?
.....
}
Run Code Online (Sandbox Code Playgroud)
==编辑 - 更新== Mehrdad 与MSDN的链接,虽然有用但让我感到困惑.我会引用,看看是否有人可以为我取消这个
当callvirt方法指令以约束thisType作为前缀时,指令执行如下:
如果thisType是引用类型(而不是值类型),则取消引用ptr并将其作为'this'指针传递给方法的callvirt.
如果thisType是一个值类型而thisType实现了方法,那么ptr将被未修改地传递为调用方法指令的'this'指针,用于通过thisType实现方法.
如果thisType是一个值类型而thisType没有实现方法,则ptr被解除引用,装箱,并作为'this'指针传递给callvirt方法指令.
那么这是否意味着如果我没有在我的结构类型上明确地实现ToString(),它将落入最后一个案例并被装箱?或者我在某处误解了它?
如果
thisType
是值类型并且thisType
没有实现方法,则ptr被解除引用,装箱,并作为"this"指针传递给callvirt方法指令.当方法定义只能出现最后这种情况下
Object
,ValueType
或Enum
,而不是由覆盖thisType
.在这种情况下,装箱会导致原始对象的副本.
答案是肯定的,值类型是装箱的.这就是为什么覆盖ToString()
自定义结构总是一件好事.
编辑: kek444的回答是正确的.我为误读这个问题而道歉.我在这里留下答案,因为我相信它为未来的读者提供了额外的价值和相关信息.
- 如果thisType是一个值类型而thisType没有实现方法,则ptr被解除引用,装箱,并作为'this'指针传递给callvirt方法指令.
最后一种情况只有在Object,ValueType或Enum上定义方法并且不被thisType覆盖时才会发生.在这种情况下,装箱会导致原始对象的副本.但是,由于Object,ValueType和Enum的方法都没有修改对象的状态, 因此无法检测到此事实.
因此,人们不能写一个程序来证明拳击正在发生.只有通过查看IL并完全理解指令的constrained
前缀才能看出它callvirt
.
来自http://download.microsoft.com/download/3/8/8/388e7205-bc10-4226-b2a8-75351c669b09/CSharp%20Language%20Specification.doc的C#语言规范的第11.3.5节(http:/ /msdn.microsoft.com/en-us/vcsharp/aa336809.aspx):
当结构类型覆盖从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>();
}
}
Run Code Online (Sandbox Code Playgroud)
该计划的输出是:
1
2
3
Run Code Online (Sandbox Code Playgroud)
虽然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>();
}
}
Run Code Online (Sandbox Code Playgroud)
第一次调用Increment会修改变量x中的值.这不等于对Increment的第二次调用,后者修改了x的盒装副本中的值.因此,该程序的输出是:
0
1
1
Run Code Online (Sandbox Code Playgroud)
有关装箱和拆箱的更多详细信息,请参见§4.3.
不,当你调用ToString
或者GetHashCode
它是由你的结构实现时,它不会被装箱(为什么应该这样?constrained
IL指令会处理它。)当你调用非虚拟方法(或结构中未重写的虚拟方法)时,它会被装箱System.Object
(它的基类),即GetType
/ MemberwiseClone
。
更新:对于可能造成的误解深表歉意。我在写答案时考虑到了重写结构中的方法(这就是为什么我提到非虚拟方法需要装箱,我应该更明确地不要让读者感到困惑,特别是因为我错过了您关于不重写该方法的声明),就好像您不重写它,该Object.ToString
方法期望它的第一个参数(对 的引用this
)是引用类型(Object
实例)。显然,该值必须在该调用中装箱(因为它是基类中的调用。)
然而,关键是,在值类型上调用虚拟方法的本质不会导致发出box
指令(与非虚拟方法不同,Object
它总是会导致发出显式box
指令。)callvirt
如果满足以下条件,则指令将执行装箱:它必须求助于实现Object.ToString
(正如您在更新的问题中提到的那样),就像您将结构传递给需要object
参数的方法一样。
归档时间: |
|
查看次数: |
4996 次 |
最近记录: |