(作为研究回答这个问题的结果,我(我想我已经!)确定答案是“不”。但是,我必须在几个不同的地方看看,所以我认为这个问题仍有价值。但如果社区投票结束,我不会感到沮丧。)
例如:
void f<T>(T val) where T : IComparable
{
val.CompareTo(null);
}
void g()
{
f(4);
}
4
装箱了吗?我知道明确地将值类型转换为它实现触发装箱的接口:
((IComparable)4).CompareTo(null); // The Int32 "4" is boxed
我不知道的是,将值类型作为具有接口约束的泛型参数传递是否等于执行强制转换 - 语言“其中T是IC Comparable”类似于建议转换,但只是转动{{ 1}}进入T
似乎会破坏通用的全部目的!
为了澄清,我想确保上述代码中都没有发生这些事情:
IComparable
调用g
时,由于f(4)
的参数类型存在4
约束,IComparable
会转换为IComparable
f
内,f
未将val.CompareTo(null)
从val
投射至Int32
以致{{1} }}。但我想了解一般情况;不仅仅是IComparable
和CompareTo
s会发生什么。
现在,如果我将以下代码放入LinqPad:
int
然后检查生成的IL:
IComparable
很明显,拳击是按照预期的显式转换进行的,但void Main()
{
((IComparable)4).CompareTo(null);
f(4);
}
void f<T>(T val) where T : IComparable
{
val.CompareTo(null);
}
本身 * 或IL_0001: ldc.i4.4
IL_0002: box System.Int32
IL_0007: ldnull
IL_0008: callvirt System.IComparable.CompareTo
IL_000D: pop
IL_000E: ldarg.0
IL_000F: ldc.i4.4
IL_0010: call UserQuery.f
f:
IL_0000: nop
IL_0001: ldarga.s 01
IL_0003: ldnull
IL_0004: constrained. 01 00 00 1B
IL_000A: callvirt System.IComparable.CompareTo
IL_000F: pop
IL_0010: ret
中的呼叫站点都没有拳击。这是个好消息。但是,这也只是一种类型的一个例子。这种缺乏拳击的东西是否适用于所有情况?
* This MSDN article讨论了f
前缀,并声明只要被调用的方法,将其与Main
结合使用就不会触发值类型的装箱在类型本身上实现(而不是基类)。我不确定的是,当我们到达这里时,类型是否总是 一个值类型。
答案 0 :(得分:6)
正如您所知,当struct
传递给通用方法时,它不会被装箱。
Runtime为每个&#34; Type Argument&#34;创建新方法。当您使用值类型调用泛型方法时,您实际上正在调用为各个值类型创建的专用方法。所以不需要拳击。
当调用未在结构类型中直接实现的接口方法时,将发生装箱。 Spec在这里调用它:
如果thisType是值类型,并且thisType没有实现方法 然后ptr被解除引用,装箱,并作为&#39;这个&#39;指针 callvirt方法指令。
最后一种情况只有在Object上定义方法时才会发生, ValueType或Enum,不会被thisType覆盖。在这种情况下, 拳击会导致原始对象的副本。然而, 因为Object,ValueType和Enum的方法都没有修改 对象的状态,这个事实无法被发现。
所以,只要你明确[1]在你的struct本身实现接口成员,就不会发生装箱。
How, when and where are generic methods made concrete?
1.不要与Explicit接口实现混淆。这就是说你的接口方法应该在struct本身而不是它的基本类型中实现。
答案 1 :(得分:1)
一个简单的测试就是简单地创建一个可变结构,其中包含一个可以改变它的接口方法。从泛型方法中调用该接口方法,并查看原始结构是否已发生变异。
public interface IMutable
{
void Mutate();
int Value { get; }
}
public struct Evil : IMutable
{
public int value;
public void Mutate()
{
value = 9;
}
public int Value { get { return value; } }
}
public static void Foo<T>(T mutable)
where T : IMutable
{
mutable.Mutate();
Console.WriteLine(mutable.Value);
}
static void Main(string[] args2)
{
Evil evil = new Evil() { value = 2 };
Foo(evil);
}
这里我们看到9打印出来,这意味着实际变量是变异的,而不是副本,所以struct
没有被装箱。
答案 2 :(得分:0)
我以Servy给出的答案为基础,我相信我的答案更具解释性,它证明了所要求的行为。
代码创建实现接口方法的结构和类。此方法试图使它们变异。该代码从该通用方法调用该接口方法,然后将该结构转换为该接口,然后再为该类调用。输出是非常不言自明的,它表明传递的结构在未强制转换为接口之前不会被装箱。另外,我添加了一些IL代码来查看何时发生装箱。
using System;
namespace ConsoleApp
{
public interface IMutable
{
void Mutate();
int Value { get; }
}
public struct EvilStruct: IMutable
{
public int value;
public void Mutate()
{
value++;
}
public int Value { get { return value; } }
}
public class EvilClass : IMutable
{
public int value;
public void Mutate()
{
value++;
}
public int Value { get { return value; } }
}
class Program
{
public static void Foo<T>(T mutable)
where T: IMutable
{
mutable.Mutate();
}
static void Main(string[] args)
{
EvilStruct Struct = new EvilStruct() { value = 1 };
Foo(Struct);
//Shows 1 after calling Mutate on value type
Console.WriteLine(Struct.Value);
IMutable YetAnotherStruct = new EvilStruct() { value = 1 };
Foo(YetAnotherStruct);
//Shows 2 after calling Mutate on value type
Console.WriteLine(YetAnotherStruct.Value);
EvilClass Class = new EvilClass() { value = 1 };
Foo(Class);
//Shows 2 after calling Mutate on ref type
Console.WriteLine(Class.Value);
Console.ReadLine();
}
}
}
输出: 1个 2 2
这是Main方法的IL代码。您可以在IL_0038看到拳击发生:
Program.Main:
IL_0000: nop
IL_0001: ldloca.s 03
IL_0003: initobj UserQuery.EvilStruct
IL_0009: ldloca.s 03
IL_000B: ldc.i4.1
IL_000C: stfld UserQuery+EvilStruct.value
IL_0011: ldloc.3
IL_0012: stloc.0 // Struct
IL_0013: ldloc.0 // Struct
IL_0014: call UserQuery+Program.Foo<EvilStruct>
IL_0019: nop
IL_001A: ldloca.s 00 // Struct
IL_001C: call UserQuery+EvilStruct.get_Value
IL_0021: call System.Console.WriteLine
IL_0026: nop
IL_0027: ldloca.s 03
IL_0029: initobj UserQuery.EvilStruct
IL_002F: ldloca.s 03
IL_0031: ldc.i4.1
IL_0032: stfld UserQuery+EvilStruct.value
IL_0037: ldloc.3
IL_0038: box UserQuery.EvilStruct
IL_003D: stloc.1 // YetAnotherStruct
IL_003E: ldloc.1 // YetAnotherStruct
IL_003F: call UserQuery+Program.Foo<IMutable>
IL_0044: nop
IL_0045: ldloc.1 // YetAnotherStruct
IL_0046: callvirt UserQuery+IMutable.get_Value
IL_004B: call System.Console.WriteLine
IL_0050: nop
IL_0051: newobj UserQuery+EvilClass..ctor
IL_0056: dup
IL_0057: ldc.i4.1
IL_0058: stfld UserQuery+EvilClass.value
IL_005D: stloc.2 // Class
IL_005E: ldloc.2 // Class
IL_005F: call UserQuery+Program.Foo<EvilClass>
IL_0064: nop
IL_0065: ldloc.2 // Class
IL_0066: callvirt UserQuery+EvilClass.get_Value
IL_006B: call System.Console.WriteLine
IL_0070: nop
IL_0071: call System.Console.ReadLine
IL_0076: pop