Nell'ultimo quiz (#47) è stato l'array initializer:
int[] a = new int[3]{1, 2, 3};
a generare la classe '<PrivateImplementationDetails>' di cui proverò a parlere in questo post.
Cioè, cambiando l'array initializer con:
int[] a = new int[3];
a[0] = 1;
a[1] = 2;
a[2] = 3;
lo snippet del quiz avrebbe stampato a console abc (la risposta B e non C).
Nel nostro caso, il codice IL generato per questa classe non documentata è:
.class private auto ansi '<PrivateImplementationDetails>'
extends [mscorlib]System.Object
{
.class explicit ansi sealed nested private '$$struct0x6000001-1'
extends [mscorlib]System.ValueType
{
.pack 1
.size 12
}
.field static assembly valuetype
'<PrivateImplementationDetails>'/'$$struct0x6000001-1'
'$$method0x6000001-1' at D_00002050
}
.data D_00002050 = bytearray (01 00 00 00 02 00 00 00 03 00 00 00)
A parte i nomi degli identificatori, il costruttore inesistente e il campo mappato '$$method0x6000001-1', il resto potrebbe essere generato dal seguente snippet Visual Basic .NET:
Imports System.Runtime.InteropServices
Friend Class [<PrivateImplementationDetails>]
<StructLayout(LayoutKind.Explicit, Pack := 1, Size := 12)> _
Private Structure [$$struct0x6000001-1]
' qui manca il campo mappato [$$method0x6000001-1]
' di tipo [<PrivateImplementationDetails>'/'$$struct0x6000001-1]
' all'array di byte (01 00 00 00 02 00 00 00 03 00 00 00)
End Structure
End Class
con l'aiuto dell'attributo pseudo custom System.Runtime.InteropServices.StructLayoutAttribute. Perché VB .NET e non C#? Perché C# decora i tipi con il flag beforefieldinit che non appare nel codice IL della classe '<PrivateImplementationDetails>' (e nel nostro caso questo aspetto è importante: "beforefieldinit instructs the CLI that it need not initialize the type before a static method is called" - ECMA-335, Partition II, 9.1.6). La dimensione della struttura (.size 12) è evidente: il prodotto tra il numero degli elementi (3) e la dimensione in byte di un elemento (4). Avendo il LayoutKind.Explicit, forse il valore del Pack non è importante.
Un'altra cosa da notare è che, il compilatore C# decide di generare la classe '<PrivateImplementationDetails>' solo per array initializer di array con almeno 3 elementi (indipendentemente dalla dimensione di un elemento). Cioè, per un array initializer:
int[] a = new int[2]{1, 2};
avremmo ottenuto di nuovo la risposta B al posto di C. Questo vale solo per i value type! La spiegazione è semplice e molto bella: i dati dell'array (.data), come si può anche notare dal codice IL, si trovano fuori da ogni tipo defnito nell'assembly appunto per poter essere condivisi e quindi non possono essere raggiunti dal garbage collector! Quindi, per un array initializer anche con più di 3 elementi ma con il tipo degli elementi non un value type:
object[] a = new object[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
la risposta è di nuovo B (la classe '<PrivateImplementationDetails>' non viene generata).
In fine notiamo che gli elementi dell'array (gli interi 1, 2 e 3) sono registrati in formato little endian (perché così è la macchina su cui ho compilato) e in rappresentazione esadecimale.
In Rotor troverete PN_GLOBALFIELD come predifined name per '<PrivateImplementationDetails>' per il compilatore C#. Questo vuol dire che i campi globali utilizzati internamente dal compilatore C# sono implementati con l'aiuto di questa classe. I campi globali sono i campi nello scope di un modulo (di un assembly) anziché di una classe. In pratica, abbiamo visto come il campo mappato "fa da" campo globale.
Tutto questo discorso vale solo per il compilatore C#. Se traduco il quiz in Visual Basic .NET:
Imports System
Imports System.Reflection
Class Foo
Public Sub New()
Dim a() As Integer = New Integer(2) {1, 2, 3}
For Each i As Integer In a
Console.Write(i)
Next
Console.WriteLine()
End Sub
Shared Sub Main()
Try
Activator.CreateInstance([Assembly].GetExecutingAssembly().GetTypes()(2))
Catch e As IndexOutOfRangeException
Console.WriteLine("abc")
End Try
End Sub
End Class
il risultato dell'esecuzione di questo snippet sarà abc stampato a console (la risposta B a differenza di C# dove la risposta è C).
La frase del giorno: "V'è una nostalgia delle cose che non ebbero mai un cominciamento" - Carmelo Bene
(Aggiornamento 03/04/05): Confermo che il valore del Pack non è necessario: "The .pack directive shall not be supplied for any type with explicit layout control" (ECMA-335, Partition II, 9.7)