看代码片段
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(); } }
.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
The constrained prefix can also be used for invocation of interface methods on value types,because the value type method implementing the interface method can be changed using a MethodImpl. If the constrained prefix is not used,the compiler is forced to choose which of the value type’s methods to bind to at compile time. Using the constrained prefix allows the MSIL to bind to the method that implements the interface method at run time,rather than at compile time.
解决方法
它取决于…你具体说你不想要一个泛型类…唯一的其他选项是非泛型类中的通用方法.唯一的其他时间,你可以让编译器发出一个受限制的调用,如果你在一个结构体上调用ToString(),GetHashCode()或Equals()(from(object)),因为那些被约束 – 如果struct有一个覆盖会打电话如果没有覆盖,它们将被调用.这就是为什么你应该总是重写这3个结构体; p但是我离题.一个简单的例子将是一个使用一些静态方法的实用程序类 – 扩展方法将是一个理想的例子,因为您也可以获得编译器在公共/隐式API和扩展/显式API之间自动切换的优点,而无需您更改代码.例如,以下(显示隐式和显式实现)都没有拳击,一个调用和一个约束的callvirt,它将通过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 }
扩展方法的一个不足之处在于,它正在堆栈上执行一个额外的结构体(见ldloc.0)副本,如果它是超大的,或者它是一个变异的方法(可能是一个问题)你应该避免反过来).如果是这种情况,ref参数是有帮助的,但请注意,扩展方法不能具有参考参数 – 因此您不能使用扩展方法.但请考虑:
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 }
仍然没有拳击,但现在我们也不会复制结构,即使是明确的情况.