Sunny Ahuwanya's Blog

Mostly notes on .NET and C#

Exploring System.Void Part I

When calling a method in a dynamically created instance of a type that is discovered at runtime, it's useful to discover the types of the method parameters and the method return type. This information is needed to successfully call the method and retrieve its return value.

It's also useful to discover if the method doesn't have a return type, so that we know not to expect a return value.
One way to find out is to check if the MethodBase.Invoke call returns a null, since a dynamically invoked method that has no return type will return a null. However, this is not the best approach for the following reasons.

Firstly, any method that returns a reference type can return a null, rendering such null checks unreliable.

Secondly, this approach cannot be used to determine the return type before invoking the method.

Fortunately, MethodInfo objects have a ReturnType property that developers can inspect to determine the return type before invoking the method. For instance, if the method returns a string type, MethodInfo.ReturnType will evaluate to typeof(string). It would seem that the natural way to determine if a method does not define a return type would be to check if MethodInfo.ReturnType == typeof(null), however, that won't work because the expression typeof(null) is illegal in C#. Null is not a type.

What's needed is a specially marked type that can be used in the typeof() evaluation to determine that the method does not have a return type. Think about that for a few seconds to get a feel of how counter-intuitive that sounds.
The issue with this approach is that if a method returns an instance of the proposed special type, then the test becomes inconclusive and unreliable. To prevent this from happening, the runtime must bar creation of methods that return this special type.

Enter the System.Void structure. This type is specially designated by .NET for just this purpose. In C#, the void keyword is an alias for the System.Void structure, thus MethodInfo.ReturnType == typeof(void) is a reliable means of discovering if a method has a return type. As expected, no method is allowed to define System.Void as its return type.

This solution looks good and seems like a perfect answer to the "no return type detection" problem. However, there may be a lot more involved. C# and the CLR strangely takes it one step further by disallowing instantiation of the System.Void type by any means. Allow me to demonstrate:

Attempt 1: Try to instantiate System.Void normally

static void Main()
{
    object o = new System.Void();
    //object o = new void() is illegal in C# lingo
}
You'll get a "System.Void cannot be used from C# -- use typeof(void) to get the void type object" (CS0673) error message if you try to compile the code.

Attempt 2: Try to instantiate System.Void dynamically

static void Main(string[] args)
{

    object o = Activator.CreateInstance(typeof(void));
            
}	
It compiles, but when you try to run it, you get a "Cannot dynamically create an instance of System.Void." error message.

Attempt 3: Bypass the C# compiler, Try to create it directly from IL

// Metadata version: v2.0.50727
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
  .ver 2:0:0:0
}
.assembly ConsoleApplication1
{

  .hash algorithm 0x00008004
  .ver 1:0:0:0
}
.module ConsoleApplication1.exe
// MVID: {47CAF74F-C24A-400E-A0F9-26EB27500120}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x00D10000


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

.class private auto ansi beforefieldinit Program
       extends [mscorlib]System.Object
{
  .field public static valuetype [mscorlib]System.Void o
  .method private hidebysig static void  Main() cil managed
  {
    .entrypoint
    // Code size       12 (0xc)
    .maxstack  8
    IL_0000:  ldsflda    valuetype [mscorlib]System.Void Program::o
    IL_0005:  initobj    [mscorlib]System.Void
    IL_000b:  ret
  } // end of method Program::Main

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // Code size       7 (0x7)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  ret
  } // end of method Program::.ctor

} // end of class Program

The IL code above is equivalent to the following C# code
using System;

class Program
{
	public static System.Void o;
	static void Main()
	{
		o = new System.Void();
	}
}
If you compile the IL code with ilasm, It compiles successfully, however it fails verification when you run Peverify against the compiled assembly. If you try to run the assembly, it runs into a fatal InvalidProgramException exception.
If you replace occurrences of System.Void in the IL code to any other struct in the System namespace (besides the ones that map to simple types -- they have a different syntax in IL), the IL code will compile and run smoothly.

From the examples shown above, we have established that the C# compiler and runtime prevents instantiation of the System.Void type. In my next post, We'll explore the System.Void type some more and try to figure out why the runtime doesn't want it to exist.

Happy new year!!