当调用递归泛型接口上的扩展方法时,结构实例是否会被装箱?
Do struct instances get boxed when an extension method on a recursive generic interface is called?
我有一个 'recursive generic interface':
public interface MyInterface<T> where T : MyInterface<T>
{
T DoSomething();
}
并且我在上面定义了一个扩展方法:
public static class MyExtensions
{
public static T DoSomethingElse<T>(this T t)
where T : MyInterface<T>
{
Console.WriteLine("DoSomethingElse was called.");
return t.DoSomething();
}
}
然后我在结构中实现了接口:
public struct MyStruct : MyInterface<MyStruct>
{
public MyStruct DoSomething()
{
Console.WriteLine("DoSomething was called.");
return new MyStruct();
}
}
然后我在main中调用了扩展方法:
public class Program
{
static void Main(string[] args)
{
MyStruct x = new MyStruct();
MyStruct y = x.DoSomethingElse();
Console.ReadKey();
}
}
问题:调用DoSomethingElse
时,objectMyStruct x
是装箱到MyInterface
,还是扩展方法直接运行在 x
?
我有理由相信两者:
- x 是装箱的:如果接口不是递归的,就会出现这种情况,所以这里可能就是这种情况,扩展方法需要
MyInterface
类型的东西来操作,所以装箱发生
- x 未装箱:参数
T t
直接表示结构体MyStruct
,因此可以直接传递而无需装箱。
我真的很努力地想出一种方法来测试它,但我找不到一种方法来捕捉结构被强制转换为接口类型的那一刻。
对于这个残酷的标题,我深表歉意,但我无法用更好的措辞表达,如果您能用更简单的术语描述问题,请随时编辑它。
没有拳击。
您可以通过将您的代码放入 sharplab here 来看到这一点。没有 box
说明。在某些情况下,可以 限制虚拟调用,但这里不行。
如果将结构转换为它的接口类型,那将框:
MyInterface x = new MyStruct();
但是,像这样调用一个泛型方法(无论泛型类型参数是否被约束为接口类型),并没有框。当您调用 MyExtensions.DoSomethingElse<MyStruct>
时,JIT 会发出该方法的新实现,它专门使用 MyStruct
- 因此,不需要装箱。
MyExtensions.DoSomethingElse
执行 constrained virtual call 来调用 DoSomething
方法。这主要用于编译器在运行时不确定目标是值类型还是引用类型时,它基本上表示 "JIT, you figure this one out"。具体来说:
- If
thisType
is a value type and thisType
implements method
then ptr
is passed unmodified as the 'this' pointer to a call
method
instruction, for the implementation of method
by thisType
.
- If
thisType
is a value type and thisType
does not implement method
then ptr
is dereferenced, boxed, and passed as the 'this' pointer to the callvirt
method
instruction.
在这里,我们遇到了第一种情况,因此没有装箱发生。如果您调用 t.GetHashCode()
或 t.GetType()
,那么 JIT 将向框 t
.
发送指令
我有一个 'recursive generic interface':
public interface MyInterface<T> where T : MyInterface<T>
{
T DoSomething();
}
并且我在上面定义了一个扩展方法:
public static class MyExtensions
{
public static T DoSomethingElse<T>(this T t)
where T : MyInterface<T>
{
Console.WriteLine("DoSomethingElse was called.");
return t.DoSomething();
}
}
然后我在结构中实现了接口:
public struct MyStruct : MyInterface<MyStruct>
{
public MyStruct DoSomething()
{
Console.WriteLine("DoSomething was called.");
return new MyStruct();
}
}
然后我在main中调用了扩展方法:
public class Program
{
static void Main(string[] args)
{
MyStruct x = new MyStruct();
MyStruct y = x.DoSomethingElse();
Console.ReadKey();
}
}
问题:调用DoSomethingElse
时,objectMyStruct x
是装箱到MyInterface
,还是扩展方法直接运行在 x
?
我有理由相信两者:
- x 是装箱的:如果接口不是递归的,就会出现这种情况,所以这里可能就是这种情况,扩展方法需要
MyInterface
类型的东西来操作,所以装箱发生 - x 未装箱:参数
T t
直接表示结构体MyStruct
,因此可以直接传递而无需装箱。
我真的很努力地想出一种方法来测试它,但我找不到一种方法来捕捉结构被强制转换为接口类型的那一刻。
对于这个残酷的标题,我深表歉意,但我无法用更好的措辞表达,如果您能用更简单的术语描述问题,请随时编辑它。
没有拳击。
您可以通过将您的代码放入 sharplab here 来看到这一点。没有 box
说明。在某些情况下,可以 限制虚拟调用,但这里不行。
如果将结构转换为它的接口类型,那将框:
MyInterface x = new MyStruct();
但是,像这样调用一个泛型方法(无论泛型类型参数是否被约束为接口类型),并没有框。当您调用 MyExtensions.DoSomethingElse<MyStruct>
时,JIT 会发出该方法的新实现,它专门使用 MyStruct
- 因此,不需要装箱。
MyExtensions.DoSomethingElse
执行 constrained virtual call 来调用 DoSomething
方法。这主要用于编译器在运行时不确定目标是值类型还是引用类型时,它基本上表示 "JIT, you figure this one out"。具体来说:
- If
thisType
is a value type andthisType
implementsmethod
thenptr
is passed unmodified as the 'this' pointer to acall
method
instruction, for the implementation ofmethod
bythisType
.- If
thisType
is a value type andthisType
does not implementmethod
thenptr
is dereferenced, boxed, and passed as the 'this' pointer to thecallvirt
method
instruction.
在这里,我们遇到了第一种情况,因此没有装箱发生。如果您调用 t.GetHashCode()
或 t.GetType()
,那么 JIT 将向框 t
.