13

see code snippet

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();
    }
}

here is IL code for call()

.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 

see reference for constrained

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.

That means it will call one method containing interface method code of f0 without boxing the struct.

Do any other ways caling interface method without boxing exist as above GenericClass in C#?

Matthias
  • 15,919
  • 5
  • 39
  • 84
Vince
  • 896
  • 5
  • 18
  • (I guess this should better be a comment :)) See http://stackoverflow.com/questions/3032750/structs-interfaces-and-boxing – Matthias Nov 26 '12 at 04:41
  • thanks for your reply. I have modified Title:how to call except in Generic? – Vince Nov 26 '12 at 04:46
  • 1
    To my bad, I didn't read the full post, because from the title I expected something different :) To your question: I *guess* no. – Matthias Nov 26 '12 at 05:51

2 Answers2

18

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

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

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<T>(ref T foo) where T : IFoo
{
    foo.Bar();
}

which is:

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&)

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
1

Since interfaces are treated as reference types, there is no way to call a method on a struct referred to by an interface without having to box the underlying struct first.

When using a generic method enforcing the type to implement the interface, the C# compiler simply lifts the actual implementation details and hence the calling convention to the runtime. Luckily the C# compiler is smart enough to instruct the JIT compiler that the subjected type does implement interface X and might be a struct. With this information the JIT compiler can figure out how to invoke method Y declared by interface X.

The above trick is not usable for a non-generic method call since there is no practical way for the JIT compiler to figure out the actual type represented by the argument X when X is a non sealed class or interface. Hence, there is no way for the C# compiler to generate JIT that deals with a lookup table in case the type represented by the interface passed is a non-sealed class and a direct method invocation that deals with structs and sealed classes.

When using dynamic you can, in theory, prevent boxing however the loss of performance for introducing the DLR will likely yield no benefit at all.

Polity
  • 14,734
  • 2
  • 40
  • 40
  • 4
    The DLR is pretty boxing-heavy... indeed, `dynamic` is implemented *essentially* as `object`. Not sure your last line stands up, at least without further explanation / detail. – Marc Gravell Nov 26 '12 at 07:55