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

Vin*_*nce 11 c# il

请参阅代码段

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是用于调用的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 
Run Code Online (Sandbox Code Playgroud)

参见约束的参考

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

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

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

Mar*_*ell 16

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)

这是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 
}
Run Code Online (Sandbox Code Playgroud)

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

Bar(ref expl);
Bar(ref impl);
Run Code Online (Sandbox Code Playgroud)

有:

static void Bar<T>(ref T foo) where T : IFoo
{
    foo.Bar();
}
Run Code Online (Sandbox Code Playgroud)

这是:

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&)
Run Code Online (Sandbox Code Playgroud)

有:

.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 
}
Run Code Online (Sandbox Code Playgroud)

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