This is the tenth 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#
If you are interested in the topic see the talk page
Today we are going to see a concept of type markers which we can use for low level optimizations. They are somewhat similar to templates in C++ and allow some very basic meta programming in C#. Let’s see the following code:
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 |
using System; namespace MarkerInterfaces { class Program { static void Main(string[] args) { int left = 1; int right = 2; Console.WriteLine(ChooseClass<LeftClass>(left, right)); Console.WriteLine(ChooseClass<RightClass>(left, right)); } static int ChooseClass<T>(int left, int right) { if (typeof(T) == typeof(LeftClass)) { return left; } else { return right; } } } interface IMarker { } class LeftClass : IMarker { } class RightClass : IMarker { } } |
We have a simple method which chooses one of two integers based on the generic type. When we decompile this we can see the following:
1 2 3 4 |
... 00190482 ff15644f1400 call dword ptr ds:[144F64h] (MarkerInterfaces.Program.ChooseClass[[System.__Canon, mscorlib]](Int32, Int32), mdToken: 06000003) ... 0019049c ff15644f1400 call dword ptr ds:[144F64h] (MarkerInterfaces.Program.ChooseClass[[System.__Canon, mscorlib]](Int32, Int32), mdToken: 06000003) |
We can see that we call ChooseClass
method using the same address. No surprise here — generic method doesn’t care about parameters and JIT compiler emits just one code for all types. If we decompile it we get:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
00190540 55 push ebp 00190541 8bec mov ebp,esp 00190543 57 push edi 00190544 56 push esi 00190545 53 push ebx 00190546 50 push eax 00190547 8b4508 mov eax,dword ptr [ebp+8] 0019054a 8945f0 mov dword ptr [ebp-10h],eax 0019054d 8bf9 mov edi,ecx 0019054f 8bda mov ebx,edx 00190551 8b4508 mov eax,dword ptr [ebp+8] 00190554 8b400c mov eax,dword ptr [eax+0Ch] 00190557 8b08 mov ecx,dword ptr [eax] 00190559 f7c101000000 test ecx,1 0019055f 7403 je 00190564 00190561 8b49ff mov ecx,dword ptr [ecx-1] 00190564 e85774cf72 call clr!JIT_GetRuntimeType (72e879c0) 00190569 8bf0 mov esi,eax 0019056b b9e04e1400 mov ecx,144EE0h (MT: MarkerInterfaces.LeftClass) 00190570 e84b74cf72 call clr!JIT_GetRuntimeType (72e879c0) 00190575 3bc6 cmp eax,esi 00190577 750a jne 00190583 |
So we can see that we get runtime type and compare it with LeftClass
.
However, things are different with structs. JIT cannot emit the same code for two different struct types because they differ in memory storage under the hood (they are values). So let’s try this:
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 |
namespace MarkerInterfaces { class Program { static void Main(string[] args) { int left = 1; int right = 2; Console.WriteLine(ChooseStruct<LeftStruct>(left, right)); Console.WriteLine(ChooseStruct<RightStruct>(left, right)); } static int ChooseStruct<T>(int left, int right) { if(typeof(T) == typeof(LeftStruct)) { return left; } else { return right; } } } struct LeftStruct : IMarker { } struct RightStruct : IMarker { } } |
And now we get:
1 2 3 4 5 |
... 00190453 ff15404e1400 call dword ptr ds:[144E40h] (MarkerInterfaces.Program.ChooseStruct[[MarkerInterfaces.LeftStruct, MarkerInterfaces]](Int32, Int32), mdToken: 06000002) ... 00190468 ff15c04e1400 call dword ptr ds:[144EC0h] (MarkerInterfaces.Program.ChooseStruct[[MarkerInterfaces.RightStruct, MarkerInterfaces]](Int32, Int32), mdToken: 06000002) ... |
Notice that now addresses are different! If we decompile ChooseStruct
for LeftStruct
we get this:
1 2 |
L0000: mov eax, ecx L0002: ret |
and for RightStruct
we get:
1 2 |
L0000: mov eax, edx L0002: ret |
Notice that parameters are different: we return ecx
or edx
, first and second parameter respectively.
JIT is able to optimize the method significantly because it knows what type it is dealing with. Next time we will see how we can use this thing to optimize code.