Are there other ways of calling an interface method of a struct without boxing except in generic classes?

前端 未结 2 444
太阳男子
太阳男子 2021-02-02 00:54

see code snippet

public interface I0
{
    void f0();
}
public struct S0:I0
{
    void I0.f0()
    {

    }
}
public class A where E :I0
{
    public E          


        
2条回答
  •  孤城傲影
    2021-02-02 01:17

    It depends... you specifically say you don't want a generic class... the only other option is a generic method in a non-generic class. The only other time you can get the compiler to emit a constrained call is if you call ToString(), GetHashCode() or Equals() (from object) on a struct, since those are then constrained - if the struct has an override they will be call; if it doesn't have an override, they will be callvirt. Which is why you should always override those 3 for any struct ;p But I digress. A simple example would be a utility class with some static methods - extension methods would be an ideal example, since you also get the advantage that the compiler will switch automatically between the public/implicit API and the extension/explicit API, without you ever needing to change code. For example, the following (which shows both an implicit and explicit implementation) has no boxing, with one call and one constrained+callvirt, which will implemented via call at the 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(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
        }
    }
    

    And here's the key bits of 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 
    }
    

    One downside of an extension method, though, is that it is doing an extra copy of the struct (see the ldloc.0) on the stack, which might be a problem if it is either oversized, or if it is a mutating method (which you should avoid anyway). If that is the case, a ref parameter is helpful, but note that an extension method cannot have a ref this parameter - so you can't do that with an extension method. But consider:

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

    with:

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

    which is:

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

    with:

    .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 
    }
    

    Still no boxing, but now we also never copy the struct, even for the explicit case.

提交回复
热议问题