.NET
The Microsoft .NET common language runtime (CLR) VM is responsible for running all of the .NET languages, but this example and others in this repository will use C#. Let’s find out if the CLR is also subject to the same type erasure as Java. Please consider the following program:
using System.Diagnostics;
namespace console
{
class Program
{
static void printLen<T>(List<T> list) {
Console.Out.WriteLine(list.Count);
}
static void Main(string[] args)
{
var ints = new List<Int32>();
ints.Add(1);
ints.Add(2);
ints.Add(3);
var strs = new List<String>();
strs.Add("Hello");
strs.Add("world");
printLen(ints);
printLen(strs);
Debugger.Break();
}
}
}
Just like the Java example, the above program defines two variables using C#’s generic list type, List
:
ints
: a list ofInt32
valuesstrs
: a list ofString
values
What do ints
and strs
look like at runtime? Follow the instructions below to find out:
Launch the container:
docker run -it --rm --cap-add=SYS_PTRACE --security-opt seccomp=unconfined go-generics-the-hard-way
Please note the
--cap-add=SYS_PTRACE --security-opt seccomp=unconfined
flags are required in order to use thelldb
debugger to attach to a .NET process.Compile the above program:
dotnet build --debug -p:UseSharedCompilation=false -o ./05-internals/dotnet/bin ./05-internals/dotnet
Load the above program into the .NET debugger:
lldb ./05-internals/dotnet/bin/dotnet
Launch a new process using the provided program and attach the debugger to it:
process launch
The process should launch and continue until the predefined breakpoint is hit:
Process 90 launched: '/go-generics-the-hard-way/05-internals/dotnet/bin/dotnet' (x86_64)
3
2
Process 90 stopped
* thread #1, name = 'dotnet', stop reason = signal SIGTRAP
frame #0: 0x00007ffff77c0571 libcoreclr.so`___lldb_unnamed_symbol15647$$libcoreclr.so + 1
libcoreclr.so`___lldb_unnamed_symbol15647$$libcoreclr.so:
-> 0x7ffff77c0571 <+1>: retq
0x7ffff77c0572 <+2>: nop
libcoreclr.so`___lldb_unnamed_symbol15648$$libcoreclr.so:
0x7ffff77c0574 <+0>: pushq %rbp
0x7ffff77c0575 <+1>: movq 0xd8(%rdi), %r12
Now that they are loaded into memory, print information about the
ints
andstrs
variables:clrstack -i -a
Dumping managed stack and managed variables using ICorDebug.
=============================================================================
Child SP IP Call Site
00007FFFFFFFDCC8 00007ffff77c0571 [NativeStackFrame]
00007FFFFFFFDD18 (null) [Internal call: 00007FFFFFFFDD18]
00007FFFFFFFDE40 00007fff7daa3777 [DEFAULT] Void System.Diagnostics.Debugger.Break() (/root/.dotnet/shared/Microsoft.NETCore.App/6.0.1/System.Private.CoreLib.dll)
PARAMETERS: (none)
LOCALS: (none)
00007FFFFFFFDE50 00007fff7e25317a [DEFAULT] Void console.Program.Main(SZArray String) (/go-generics-the-hard-way/05-internals/dotnet/bin/dotnet.dll)
PARAMETERS:
+ string[] args (empty)
LOCALS:
+ System.Collections.Generic.List`1<int> ints @ 0x7fff48008758
+ System.Collections.Generic.List`1<string> strs @ 0x7fff480087b8
00007FFFFFFFDE90 00007ffff762aa27 [NativeStackFrame]
Stack walk complete.
=============================================================================
In addition to being defined at compile-time as
List<Int32>
andList<String>
, the variablesints
andstrs
maintain their full type information at runtime asList<int>
andList<string>
(Int32
andint
are interchangeable as areString
andstring
).In fact, not only are the types not erased, but .NET maintains knowledge of the generic type from which these types were instantiated. To illustrate this we need to grab the metadata tokens for the underlying classes used by the
ints
andstrs
variables.In the previous step please note the memory addresses of each variable:
ints
:0x7fff48008758
strs
:0x7fff480087b8
Dump the
ints
object using its memory address:dumpobj 0x7fff48008758
Name: System.Collections.Generic.List`1[[System.Int32, System.Private.CoreLib]]
MethodTable: 00007fff7e2f7d20
EEClass: 00007fff7e373598
Tracked Type: false
Size: 32(0x20) bytes
File: /root/.dotnet/shared/Microsoft.NETCore.App/6.0.1/System.Private.CoreLib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007fff7e2b8080 4001fa5 8 System.Int32[] 0 instance 00007fff48008790 _items
00007fff7e2a9018 4001fa6 10 System.Int32 1 instance 3 _size
00007fff7e2a9018 4001fa7 14 System.Int32 1 instance 3 _version
00007fff7e2b8080 4001fa8 8 System.Int32[] 0 static dynamic statics NYI s_emptyArray
Record the memory address for the
EEClass
, ex.00007fff7e373598
.Dump the
EEClass
for theints
variable using the address, ex.00007fff7e373598
:dumpclass 00007fff7e373598
Class Name: System.Collections.Generic.List`1[[System.Int32, System.Private.CoreLib]]
mdToken: 0000000002000865
File: /root/.dotnet/shared/Microsoft.NETCore.App/6.0.1/System.Private.CoreLib.dll
Parent Class: 00007fff7e1fa230
Module: 00007fff7d6d4000
Method Table: 00007fff7e2f7d20
Vtable Slots: 1e
Total Method Slots: 4e
Class Attributes: 102001
NumInstanceFields: 3
NumStaticFields: 1
MT Field Offset Type VT Attr Value Name
00007fff7e2b8080 4001fa5 8 System.Int32[] 0 instance _items
00007fff7e2a9018 4001fa6 10 System.Int32 1 instance _size
00007fff7e2a9018 4001fa7 14 System.Int32 1 instance _version
00007fff7e2b8080 4001fa8 8 System.Int32[] 0 static dynamic statics NYI s_emptyArray
Note the
ints
variable ultimately uses a class with themdToken
at memory address0000000002000865
.Print the metadata for the
ints
variable:sos Token2EE System.Private.CoreLib.dll 0000000002000865
Module: 00007fff7d6d4000
Assembly: System.Private.CoreLib.dll
Token: 0000000002000865
MethodTable: 00007fff7e2dd7e8
EEClass: 00007fff7e2ec598
Name: System.Collections.Generic.List`1
Repeat the above steps for the
strs
variable, starting by dumping its object at address0x7fff480087b8
:dumpobj 0x7fff480087b8
It has an
EEClass
address of00007fff7e2ee0b0
, so dump that too:dumpclass 00007fff7e2ee0b0
Which prints a metadata token of…
0000000002000865
. The same asints
! This is because bothints
andstrs
ultimately use the same generic template,List<T>
, to build their types, and .NET maintains that information at runtime.Detach from the process:
process detach
Type
quit
to exit the debugger.Type
exit
to stop and remove the container.
In other words, generics in .NET do retain their type information at runtime. So what about Golang?
Next: Golang