Jan 26, 2012

Interfaces in IL

After a long period of silence, today I've something interesting (IMHO) (and the time to, of course :)) to post about.

Last night I was reading a book about MS IL (intermediate language) when suddenly I stumbled with an intriguing paragraph about interfaces. The "intriguing" part motivated me to write a sample application to test whenever that would be valid in C# or not.
Given the following NUnit test, how can you define a type "IAmAnInterface" so it can pass ?
 
using System;
using NUnit.Framework;

namespace Testing
{
 
 [TestFixture]
 public class BlobPost_Interface
 {
  [Test]
  public void Test()
  {
   var type = typeof(IAmAnInterface);
   Assert.That(type.IsInterface, Is.True);

   var field = type.GetField("fld");
   Console.WriteLine("    Field: {0}", field);
   Assert.That(field, Is.Not.Null);

   field.SetValue(null, 10);

   Assert.That(field.GetValue(null), Is.EqualTo(10));
  }
 }
}

Just in case you missed it, the crux here, is that the test expects IAmAnInterface to be an interface and (and here lies the issue) to define a (static) field! 

How come? An interface with fields?

Well, it happens that the C# specification (and the compiler also!) disallows such constructs, but they are actually valid in IL (as described in Common Language Infrastructure ,session 8.9.4)!

So, in order to get this to work I simply created an assembly in IL (actually I disassembled an assembly written in C# and changed it) that looks like:
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         
  .ver 4:0:0:0
}
.assembly TestString
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
  .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
                                                                                                             63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 )       // ceptionThrows.
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module TestString.dll
// MVID: {64C46A76-194E-4608-A835-B41B179AF4FA}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x004D0000


// =============== CLASS MEMBERS DECLARATION ===================

.class interface public abstract auto ansi IAmAnInterface
{
  .method public hidebysig newslot specialname abstract virtual 
          instance int32  get_value() cil managed
  {
  } // end of method IAmAnInterface::get_value
  
  .field public static int32 fld

} // end of class IAmAnInterface


// =============================================================

and referenced it in my C# test application inside VS2010 which happily showed the field in IntelliSense but gave an error when I tried to compile the code (so I resorted to reflection, and it worked) (see picture below):
What do you think?

Best

Adriano

2 comments:

Gamlor said...

My guess is that they also allowed to this for the initial J# support, no? Cause in Java this is a allowed construct.

Adriano Verona said...

Hey crazy guy! :)

That's my bet also.