除了generics类之外,还有其他方法可以调用结构的接口方法而不需要装箱吗?

请参阅代码段

public interface I0 { void f0(); } public struct S0:I0 { void I0.f0() { } } public class A where E :I0 { public E e; public void call() { e.f0(); } } 

这是用于调用的IL代码()

 .maxstack 8 L_0000: ldarg.0 L_0001: ldflda !0 Temp.A`1::e L_0006: constrained !E L_000c: callvirt instance void Temp.I0::f0() L_0011: ret 

参见约束的参考

约束前缀也可用于在值类型上调用接口方法,因为可以使用MethodImpl更改实现接口方法的值类型方法。 如果未使用约束前缀,则编译器必须在编译时选择要绑定到哪个值类型的方法。 使用约束前缀允许MSIL在运行时绑定到实现接口方法的方法,而不是在编译时绑定。

这意味着它将调用一个包含接口方法代码f0的方法而不用装箱结构。

在C#中如上所述GenericClass是否存在任何其他方式没有装箱的caling接口方法?

这取决于……你具体说你不想要generics ……唯一的另一个选择是非generics类中的generics方法 。 你可以让编译器发出constrained调用的另一个时间是你在结构上调用ToString()GetHashCode()Equals() (来自object ),因为那些被constrained – 如果structoverride它们会call ; 如果它没有 override ,它们将是callvirt 。 这就是为什么你应该总是override任何struct 3; p但我离题了。 一个简单的例子是带有一些静态方法的实用程序类 – 扩展方法将是一个理想的示例,因为您还可以获得编译器将在公共/隐式API和扩展/显式API之间自动切换的优势,而无需您改变代码。 例如,以下(显示隐式和显式实现)没有装箱,有一个call和一个constrained + callvirt ,它将通过JIT call实现:

 using System; interface IFoo { void Bar(); } struct ExplicitImpl : IFoo { void IFoo.Bar() { Console.WriteLine("ExplicitImpl"); } } struct ImplicitImpl : IFoo { public void Bar() {Console.WriteLine("ImplicitImpl");} } static class FooExtensions { public static void Bar(this T foo) where T : IFoo { foo.Bar(); } } static class Program { static void Main() { var expl = new ExplicitImpl(); expl.Bar(); // via extension method var impl = new ImplicitImpl(); impl.Bar(); // direct } } 

这是IL的关键部分:

 .method private hidebysig static void Main() cil managed { .entrypoint .maxstack 1 .locals init ( [0] valuetype ExplicitImpl expl, [1] valuetype ImplicitImpl impl) L_0000: ldloca.s expl L_0002: initobj ExplicitImpl L_0008: ldloc.0 L_0009: call void FooExtensions::Bar(!!0) L_000e: ldloca.s impl L_0010: initobj ImplicitImpl L_0016: ldloca.s impl L_0018: call instance void ImplicitImpl::Bar() L_001d: ret } .method public hidebysig static void Bar<(IFoo) T>(!!T foo) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() .maxstack 8 L_0000: ldarga.s foo L_0002: constrained. !!T L_0008: callvirt instance void IFoo::Bar() L_000d: ret } 

但是,扩展方法的一个缺点是它正在堆栈上执行struct的额外副本(请参阅ldloc.0 ),如果它是超大的,或者它是一个变异方法,这可能是一个问题(无论如何你应该避免)。 如果是这种情况,则ref参数很有用,但请注意, 扩展方法不能具有ref this参数 – 因此您无法使用扩展方法执行此操作。 但请考虑:

 Bar(ref expl); Bar(ref impl); 

有:

 static void Bar(ref T foo) where T : IFoo { foo.Bar(); } 

这是:

 L_001d: ldloca.s expl L_001f: call void Program::Bar(!!0&) L_0024: ldloca.s impl L_0026: call void Program::Bar(!!0&) 

有:

 .method private hidebysig static void Bar<(IFoo) T>(!!T& foo) cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: constrained. !!T L_0007: callvirt instance void IFoo::Bar() L_000c: ret } 

仍然没有拳击,但现在我们也永远不会复制结构,即使是明确的情况。

由于接口被视为引用类型,因此无法在接口引用的结构上调用方法,而无需先打包底层结构。

当使用强制类型实现接口的generics方法时,C#编译器只是提取实际的实现细节,从而提升运行时的调用约定。 幸运的是,C#编译器足够聪明,可以指示JIT编译器,subject类型确实实现了接口X,可能是一个struct。 有了这些信息,JIT编译器就可以弄清楚如何调用接口X声明的方法Y.

上述技巧不适用于非generics方法调用,因为当X是非密封类或接口时,JIT编译器没有实际的方法来确定参数X表示的实际类型。 因此,如果传递的接口所表示的类型是非密封类以及处理结构和密封类的直接方法调用,则C#编译器无法生成处理查找表的JIT。

当使用动态时,理论上可以防止拳击,但是引入DLR的性能损失可能根本不会产生任何好处。