Web Log di Adrian Florea

"You know you've achieved perfection in design, not when you have nothing more to add, but when you have nothing more to take away." Antoine de Saint-Exupery
posts - 440, comments - 2715, trackbacks - 3944

My Links

Archives

Post Categories

Image Galleries

.RO Blogs

.RO People

.RO Sites

Blogs

Furls

Links

vinCitori

PrivateImplementationDetails e gli array initializer in C# (ovvero il Quiz Sharp #47 dietro le quinte)

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)

Print | posted on sabato 2 aprile 2005 18:27 | Filed Under [ Quiz Sharp Carillon .NET ]

Powered by:
Powered By Subtext Powered By ASP.NET