What can you do in MSIL that you cannot do in C# or VB.NET? [closed]

All code written in .NET languages compiles to MSIL, but are there specific tasks / operations that you can do only using MSIL directly?

Let us also have things done easier in MSIL than C#, VB.NET, F#, j# or any other .NET language.

So far we have this:

  1. Tail recursion
  2. Generic Co/Contravariance (allowed in C# 4 and VB 10)
  3. Overloads which differ only in return types
  4. Override access modifiers
  5. Have a class which cannot inherit from System.Object
  6. Filtered exceptions (allowed in VB, and C# 6)
  7. Calling a virtual method of the current static class type.
  8. Get a handle on the boxed version of a value type.
  9. Do a try/fault.
  10. Usage of forbidden names.
  11. Define your own parameterless constructors for value types.
  12. Define events with a raise element.
  13. Some conversions allowed by the CLR but not by C#.
  14. Make a non main() method as the .entrypoint.
  15. work with the native int and native unsigned int types directly.
  16. Play with transient pointers
  17. emitbyte directive in MethodBodyItem
  18. Throw and catch non System.Exception types
  19. Inherit Enums (Unverified)
  20. You can treat an array of bytes as a (4x smaller) array of ints.
  21. You can have a field/method/property/event all have the same name(Unverified).
  22. You can branch back into a try block from its own catch block.
  23. You have access to the famandassem access specifier (protected internal is famorassem, but now allowed in C# 7.2 and VB 15.5)
  24. Direct access to the <Module> class for defining global functions, or a module initializer.
  25. Create and use non-zero-bound 1-based arrays
  26. Create open-instance and closed-static delegates, and delegates of getters/setters
  27. Swap two values without using a temp variable
  28. Explicit interface implementation with any name, and implementing two interface functions in one (can be done in VB)
  29. Declaring vtfixup (the equivalent of extern in C)
  30. Specifying arbitrary modopt or modreq

Solution 1:

MSIL allows for overloads which differ only in return types because of

call void [mscorlib]System.Console::Write(string)

or

callvirt int32 ...

Solution 2:

Most .Net languages including C# and VB do not use the tail recursion feature of MSIL code.

Tail recursion is an optimization that is common in functional languages. It occurs when a method A ends by returning the value of method B such that method A's stack can be deallocated once the call to method B is made.

MSIL code supports tail recursion explicitly, and for some algorithms this could be a important optimization to make. But since C# and VB do not generate the instructions to do this, it must be done manually (or using F# or some other language).

Here is an example of how tail-recursion may be implemented manually in C#:

private static int RecursiveMethod(int myParameter)
{
    // Body of recursive method
    if (BaseCase(details))
        return result;
    // ...

    return RecursiveMethod(modifiedParameter);
}

// Is transformed into:

private static int RecursiveMethod(int myParameter)
{
    while (true)
    {
        // Body of recursive method
        if (BaseCase(details))
            return result;
        // ...

        myParameter = modifiedParameter;
    }
}

It is common practice to remove recursion by moving the local data from the hardware stack onto a heap-allocated stack data structure. In the tail-call recursion elimination as shown above, the stack is eliminated completely, which is a pretty good optimization. Also, the return value does not have to walk up a long call-chain, but it is returned directly.

But, anyway, the CIL provides this feature as part of the language, but with C# or VB it has to be implemented manually. (The jitter is also free to make this optimization on its own, but that is a whole other issue.)

Solution 3:

In MSIL, you can have a class which cannot inherit from System.Object.

Sample code: compile it with ilasm.exe UPDATE: You must use "/NOAUTOINHERIT" to prevent assembler from auto inheriting.

// Metadata version: v2.0.50215
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 2:0:0:0
}
.assembly sample
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module sample.exe
// MVID: {A224F460-A049-4A03-9E71-80A36DBBBCD3}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x02F20000


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

.class public auto ansi beforefieldinit Hello
{
  .method public hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    // Code size       13 (0xd)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldstr      "Hello World!"
    IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_000b:  nop
    IL_000c:  ret
  } // end of method Hello::Main
} // end of class Hello

Solution 4:

It's possible to combine the protected and internal access modifiers. In C#, if you write protected internal a member is accessible from the assembly and from derived classes. Via MSIL you can get a member which is accessible from derived classes within the assembly only. (I think that could be pretty useful!)

Solution 5:

Ooh, I didn't spot this at the time. (If you add the jon-skeet tag it's more likely, but I don't check it that often.)

It looks like you've got pretty good answers already. In addition:

  • You can't get a handle on the boxed version of a value type in C#. You can in C++/CLI
  • You can't do a try/fault in C# ("fault" is a like a "catch everything and rethrow at the end of the block" or "finally but only on failure")
  • There are lots of names which are forbidden by C# but legal IL
  • IL allows you to define your own parameterless constructors for value types.
  • You can't define events with a "raise" element in C#. (In VB you have to for custom events, but "default" events don't include one.)
  • Some conversions are allowed by the CLR but not by C#. If you go via object in C#, these will sometimes work. See a uint[]/int[] SO question for an example.

I'll add to this if I think of anything else...