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