This is the twentieth sixth part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#
We know C# has multiple signature (interface) and implementation inheritance. The latter doesn’t support full polymorphic invocations, though, but we already fixed it. We also know how to emulate state inheritance in Java and that can be almost directly translated to C#. Today we’ll see how to hack multiple identity inheritance in C#.
Word of warning: this is super hacky and requires a lot of attention (just like most things on this blog, though).
Let’s start with the following classes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
classBase1
{
publicintfield;
publicvoidPrintInt()
{
Console.WriteLine(field);
}
}
classBase2
{
publicfloatfield;
publicvoidPrintFloat()
{
Console.WriteLine(field);
}
}
classBase3
{
publicshortfield1;
publicshortfield2;
publicvoidPrintFields()
{
Console.WriteLine(field1);
Console.WriteLine(field2);
}
}
classBase4
{
publicstringfield;
publicvoidPrintString()
{
Console.WriteLine(field);
}
}
They have the same physical structure in .NET Framework 4.5 on x86 architecture. Each instance has sync block (4 bytes), method handle (4 bytes), fields occupying 4 bytes (either one field like integer/float/string or two fields taking 2 bytes each). We’d like to create a class which can behave like any of these, just like with inheritance.
The idea is simple: we’ll create a fake class with matching size and holders for fields of each base class. We’ll dynamically change type as needed (morph the instance) and save/restore fields.
First, we need to have holders for base instances:
Notice how each subclass inherits the fields from the base class and also adds one more field for the state. Also, we inherit so we can use the subclass as a base class as needed.
We extract the current type (after last morphing) and we save all the fields on the side to the specific holder. Then, we change the type (morph) and then restore fields for new type using holder instance. We change the type by modifying the method handle in place.
It’s crucial here to understand that we assume that all instances have the same size and that the currentState field is always in the same place. We need to have the same size in each of the fake subclasses to support proper heap scanning. Otherwise GC will crash. We need to have currentState field in the same place otherwise we won’t find it after morphing.
So we start with instance of any fake subclass and create holders as needed. Next, we morph and modify fields:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Console.WriteLine("Base1");
Base1 base1=child.Morph();
base1.field=123;
base1.PrintInt();
Console.WriteLine();
Console.WriteLine("Base2");
Base2 base2=child.Morph();
base2.field=456.0f;
base2.PrintFloat();
Console.WriteLine();
Console.WriteLine("Base3");
Base3 base3=child.Morph();
base3.field1=789;
base3.field2=987;
base3.PrintFields();
Console.WriteLine();
Console.WriteLine("Base4");
Base4 base4=child.Morph();
base4.field="Abrakadabra";
base4.PrintString();
Console.WriteLine();
Console.WriteLine("Base3 again");
base3=child.Morph();
base3.PrintFields();
Console.WriteLine();
Console.WriteLine("Base2 again");
base2=child.Morph();
base2.PrintFloat();
Console.WriteLine();
Console.WriteLine("Base1 again");
base1=child.Morph();
base1.PrintInt();
Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Base1
123
Base2
456
Base3
789
987
Base4
Abrakadabra
Base3 again
789
987
Base2 again
456
Base1 again
123
So you can see that we can morph the instance and change fields, and then we can morph back and restore fields. Obviously, multithreading scenario here would be pretty tricky. However, we sort of hacked the instance to support multiple base classes in a sort of generic way.