Typy generyczne w .NET–implementacja wewnętrzna

Typy generyczne w .NET to nie żadna imitacja, a prawdziwa implementacja (w przeciwieństwie do Java), zoptymalizowana pod wieloma kątami.

Typy referencyjne są zaimplementowane w inny sposób niż value types. Załóżmy, że mamy następujący kod w c#:

class Program
{
   static void Main(string[] args)
   {
       var s1 = new CustomStack<int>();
       var s2 = new CustomStack<double>();
   }
}

class CustomStack<T>
{
   public T Value { get; set; }
   public object OtherProperty { get; set; }
   
   public string AnotherProperty { get; set; }
}

W przypadku value type zostaną wygenerowane dwie różne specjalizacje dla CustomStack. Każda próba stworzenia instancji CustomStack z innym typem, spowoduje utworzenie nowej specjalizacji. Sprawa wygląda jednak inaczej dla typów referencyjnych. Rozszerzmy przykład o:

var s1 = new CustomStack<int>();
var s2 = new CustomStack<double>();

var s3 = new CustomStack<Person>();
var s4 = new CustomStack<Customer>();

Specjalizacje dla Person i Customer będą dzielić ze sobą layout. Nie ważne jak wiele stworzymy specjalizacji, zawsze będą współdzielić ten sam layout. W .NET nie grozi nam eksplozja typów, która miałaby miejsce, gdy nie powyższa optymalizacja.

Sprawdźmy jednak to sami za pomocą debuggera windbg.exe. Zaczynamy standardowo od załadowania SOS:

.load C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll

Następnie przyjrzyjmy się aktualnie załadowanym typom za pomocą:

!dumpheap -stat

Wynik:

image

Te co nas interesują  to:

003f3d08        1           20 ConsoleApplication16.CustomStack`1[[ConsoleApplication16.Customer, ConsoleApplication16]]
003f3c4c        1           20 ConsoleApplication16.CustomStack`1[[ConsoleApplication16.Person, ConsoleApplication16]]
003f399c        1           20 ConsoleApplication16.CustomStack`1[[System.Int32, mscorlib]]
003f3a80        1           24 ConsoleApplication16.CustomStack`1[[System.Double, mscorlib]]

Najpierw udowodnimy, że typy value nie współdzielą layoutu. Za pomocą powyższych adresów możemy wylistować method table używając następujących poleceń:

!dumpmt 003f3a80        
!dumpmt 003f399c               

Wynik:

// specjalizacja dla int
0:000> !dumpmt 003f399c        
EEClass:         003f1580
Module:          003f2ed4
Name:            ConsoleApplication16.CustomStack`1[[System.Int32, mscorlib]]
mdToken:         02000005
File:            C:\Users\Piotr\Documents\Visual Studio 2013\Projects\ConsoleApplication16\ConsoleApplication16\bin\Debug\ConsoleApplication16.exe
BaseSize:        0x14
ComponentSize:   0x0
Slots in VTable: 11
Number of IFaces in IFaceMap: 0

// specjalizacja dla double
0:000> !dumpmt 003f3a80        
EEClass:         003f15e0
Module:          003f2ed4
Name:            ConsoleApplication16.CustomStack`1[[System.Double, mscorlib]]
mdToken:         02000005
File:            C:\Users\Piotr\Documents\Visual Studio 2013\Projects\ConsoleApplication16\ConsoleApplication16\bin\Debug\ConsoleApplication16.exe
BaseSize:        0x18
ComponentSize:   0x0
Slots in VTable: 11
Number of IFaces in IFaceMap: 0

To co rzuca się w oczy to adres EEClass – jest inny dla int niż dla double. W przyszłości chcę napisać post o internalach typów, ale na razie wystarczy wiedzieć, że EEClass opisuje listę metod (layout) dla danego typu. Przyjrzyjmy się zatem EEClass dla specjalizacji double.

Polecenie:

!dumpclass 003f15e0

Wynik:

Class Name:      ConsoleApplication16.CustomStack`1[[System.Double, mscorlib]]
mdToken:         02000005
File:            C:\Users\Piotr\Documents\Visual Studio 2013\Projects\ConsoleApplication16\ConsoleApplication16\bin\Debug\ConsoleApplication16.exe
Parent Class:    73713f70
Module:          003f2ed4
Method Table:    003f3a80
Vtable Slots:    4
Total Method Slots:  b
Class Attributes:    100000  
Transparency:        Critical
NumInstanceFields:   3
NumStaticFields:     0
      MT    Field   Offset                 Type VT     Attr    Value Name
73b1b454  4000001        4        System.Double  1 instance           <Value>k__BackingField
73b22554  4000002        c        System.Object  0 instance           <OtherProperty>k__BackingField
73b221b4  4000003       10        System.String  0 instance           <AnotherProperty>k__BackingField

Wyraźnie widzimy, że layout zawiera pole double:

73b1b454  4000001        4        System.Double  1 instance           <Value>k__BackingField

EEClass dla specjalizacji integer, będzie zawierało analogiczny layout ale z innym typem dla Value:

      MT    Field   Offset                 Type VT     Attr    Value Name
73b23b04  4000001        c         System.Int32  1 instance           <Value>k__BackingField
73b22554  4000002        4        System.Object  0 instance           <OtherProperty>k__BackingField
73b221b4  4000003        8        System.String  0 instance           <AnotherProperty>k__BackingField

Powróćmy teraz do typów referencyjnych. Analogicznie za pomocą !dumpmt wyświetlamy method table dla nich:

// specjalizacja Customer
0:000> !dumpmt 003f3d08        
EEClass:         003f1694
Module:          003f2ed4
Name:            ConsoleApplication16.CustomStack`1[[ConsoleApplication16.Customer, ConsoleApplication16]]
mdToken:         02000005
File:            C:\Users\Piotr\Documents\Visual Studio 2013\Projects\ConsoleApplication16\ConsoleApplication16\bin\Debug\ConsoleApplication16.exe
BaseSize:        0x14
ComponentSize:   0x0
Slots in VTable: 11
Number of IFaces in IFaceMap: 0
// specjalizacja Person
0:000> !dumpmt 003f3c4c        
EEClass:         003f1694
Module:          003f2ed4
Name:            ConsoleApplication16.CustomStack`1[[ConsoleApplication16.Person, ConsoleApplication16]]
mdToken:         02000005
File:            C:\Users\Piotr\Documents\Visual Studio 2013\Projects\ConsoleApplication16\ConsoleApplication16\bin\Debug\ConsoleApplication16.exe
BaseSize:        0x14
ComponentSize:   0x0
Slots in VTable: 11
Number of IFaces in IFaceMap: 0

Od razu widać, że EEClass jest taki sam i znajduje się pod adresem 003f1694. Poleceniem !dumpclass 003f1694 wyświetlamy jej zawartość:

      MT    Field   Offset                 Type VT     Attr    Value Name
73b24d7c  4000001        4       System.__Canon  0 instance           <Value>k__BackingField
73b22554  4000002        8        System.Object  0 instance           <OtherProperty>k__BackingField
73b221b4  4000003        c        System.String  0 instance           <AnotherProperty>k__BackingField

Co widzimy? Przede wszystkim System.__Canon jako typ dla Value. Innymi słowy, dla typów referencyjnych jest używany ten sam layout i zamiast konkretnego typu mamy System.__Canon.

To jeszcze nie koniec- w kolejnym wpisie zobaczymy kilka ciekawostek z wywoływania metod w generics…

Leave a Reply

Your email address will not be published.