Previously we have show how we can obtain an instance of System.Void
, despite some effort by the .NET team to disallow that.
However the API we used returned an object which led to the boxing of the void instance.
Having a box of nothing is not the same as having nothing, so it seems like we must take an alternative route so that we can finally hold an unboxed void.
As discussed last time, unboxed value types usually exist in the following memory locations:
- On the stack
- As part of other types
- As elements in an array
The first 2 are not possible in C# as the compiler disallows us from referencing System.Void
, citing error CS0673.
However C# is just a higher level language which compiles into CIL (Common Intermediate Language). Is the restriction on referencing System.Void
just a high-level
restriction which is ignored at lower levels?
Since I am not too well-versed in CIL, my strategy is to write programs in C#, then disassemble them into IL using ildasm lib.dll /OUT=lib.il
.
I will then use a text editor to make changes to the IL then re-assemble the DLL back using ilasm /DLL lib.il
.
Using ildasm
and ilasm
is as easy as launching the Developer Command Prompt.
We start with the following program.
struct Temp { }
static void Main()
{
var myObject = new Temp();
Console.WriteLine(myObject);
}
Our goal is to modify the IL such that instead of creating a new Temp()
, we create a new System.Void
. Using the process explained earlier, we write the following CIL.
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 25 (0x19)
.maxstack 1
.locals init (valuetype [System.Runtime]System.Void V_0)
IL_0000: ldloca.s V_0
IL_0002: initobj [System.Runtime]System.Void
IL_0008: ldloc.0
IL_0009: box [System.Runtime]System.Void
IL_000e: call instance class [System.Runtime]System.Type [System.Runtime]System.Object::GetType()
IL_0013: call void [System.Console]System.Console::WriteLine(object)
IL_0018: ret
} // end of method Program::Main
One ildasm
later and voila; "Operation completed successfully". We run the program and...
Unhandled exception. System.InvalidProgramException: Common Language Runtime detected an invalid program.
at AbusingVoid.Program.Main(String[] args)
So it seems that C# is just being nice and stopping us from creating invalid programs.
It seems like a long shot, but we can try a similar operation with System.Void
as a field within a class.
Void as a field
The following C# program has a class Holder
which contains a single field of type Temp
. The object is constructed then sent off for all it's members to be printed to the console.
struct Temp { }
class Holder { public Temp value; }
static void Main()
{
var myObject = new Holder();
PrintObject(myObject);
}
static void PrintObject(Holder h)
{
var members = h.GetType().GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
foreach (var member in members)
{
var value = member.GetValue(h);
Console.WriteLine($"{member.Name}: {value}");
}
}
This time we will change the class definition of Holder
to the following.
.class auto ansi nested private beforefieldinit Holder
extends [System.Runtime]System.Object
{
.field public valuetype [System.Runtime]System.Void 'value'
// [ Constructor code removed for brevity ]
} // end of class Holder
We execute the program and...
Hello!
value: System.Void
Fantastic! It seems like we have a class which contains a field of type System.Void
.
We can't use the field directly through code as the CLR throws System.InvalidProgramException
as seen earlier, but we can inspect attributes of the resulting class.
It is important to draw a distinction between the Holder
class and a boxed void;
the "header" of a box is mostly a pointer to the System.Type
which is being boxed (in our case, typeof(void)
),
while the void in Holder
does not have this header - instead, it is the Holder
class, which can contain many other fields, which has the reference to typeof(Holder)
.
This also means we can add more fields to Holder
, some of the of type Void
. What would this look like? We explore this next time.
Join the Discussion
You must be signed in to comment.