请参阅代码段
public interface I0
{
void f0();
}
public struct S0:I0
{
void I0.f0()
{
}
}
public class A<E> 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>::e
L_0006: constrained !E
L_000c: callvirt instance void Temp.I0::f0()
L_0011: ret
参考参考 受限
约束前缀也可用于在值类型上调用接口方法,因为可以使用MethodImpl更改实现接口方法的值类型方法。如果未使用约束前缀,则编译器必须在编译时选择要绑定到哪个值类型的方法。使用约束前缀允许MSIL在运行时绑定到实现接口方法的方法,而不是在编译时绑定。
这意味着它将调用一个包含接口方法代码f0的方法而不用装箱结构。
在C#中如上所述GenericClass是否存在任何其他方式没有装箱的caling接口方法?
这取决于......你具体说你不想要通用 类......唯一的另一种选择是通用的 方法 在一个 非通用 类。唯一的 其他 你可以让编译器发出一个 constrained
如果你打电话就打电话 ToString()
, GetHashCode()
要么 Equals()
(从 object
) struct
那时候那些 constrained
- 如果 struct
有一个 override
他们会 call
;如果它 不 有一个 override
, 他们会 callvirt
。这就是为什么你应该永远 override
任何3个 struct
;但我离题了。一个简单的例子是带有一些静态方法的实用程序类 - 延期 方法将是一个理想的示例,因为您还可以获得编译器将在公共/隐式API和扩展/显式API之间自动切换的优势,而无需您更改代码。例如,以下(显示隐式和显式实现)没有装箱,只有一个 call
一个 constrained
+callvirt
,将通过实施 call
在JIT:
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<T>(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<valuetype ExplicitImpl>(!!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
)在堆栈上,哪个 威力 如果它是超大的,或者它是一种变异方法(无论如何你应该避免),这是一个问题。如果是这样的话,a ref
参数很有帮助,但要注意一个 延期 方法不能有 ref this
参数 - 所以你不能用扩展方法做到这一点。但请考虑:
Bar(ref expl);
Bar(ref impl);
有:
static void Bar<T>(ref T foo) where T : IFoo
{
foo.Bar();
}
这是:
L_001d: ldloca.s expl
L_001f: call void Program::Bar<valuetype ExplicitImpl>(!!0&)
L_0024: ldloca.s impl
L_0026: call void Program::Bar<valuetype ImplicitImpl>(!!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
}
仍然没有拳击,但现在我们也永远不会复制结构,即使是 明确的 案件。
由于接口被视为引用类型,因此无法在接口引用的结构上调用方法,而无需先打包底层结构。
当使用强制类型实现接口的通用方法时,C#编译器只是提升实际的实现细节,从而提升运行时的调用约定。幸运的是,C#编译器足够聪明,可以指示JIT编译器,subject类型确实实现了接口X,可能是一个struct。有了这些信息,JIT编译器就可以弄清楚如何调用接口X声明的方法Y.
上述技巧不适用于非泛型方法调用,因为当X是非密封类或接口时,JIT编译器没有实际的方法来确定参数X表示的实际类型。因此,如果传递的接口所表示的类型是非密封类以及处理结构和密封类的直接方法调用,则C#编译器无法生成处理查找表的JIT。
当使用动态时,理论上可以防止拳击,但是引入DLR的性能损失可能根本不会产生任何好处。