编辑: kek444's answer 是正确的。我很抱歉误读了这个问题。我在这里留下我的答案,因为我相信它对未来的读者有额外的价值和相关信息。
我也认为Mehrdad's answer中reference的这句话特别发人深省:
- 如果 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。