Questo post di David Hayden,
segnalato da Michele, che riprende un paragrafo da
questo libro (non l'ho ancora visto nelle librerie ma
un altro libro di
Christian Gross mi è piaciuto molto) mi ha incuriosito perché sia in C# (
CS0451) che in VB.NET (
BC32103) è impossibile avere insieme una constraint
struct/
Structure e una constraint
new()/
New. E quindi mi sono chiesto come mai "
without the class constraint, the compiler doesn't know if the type T
is a value type or reference type and hence has to check for both"? Se
struct e
new() non possono stare insieme non significa che il type parameter è obbligatoriamente un reference class?
No! Possiamo tranquillamente passare dei value type a un type parameter vincolato da
new(). Cioè, è lecito avere istanze di Foo<
int> se abbiamo definito
class Foo<T>
where T:
new(){}.
I seguenti due snippet, in C# respettivamente VB.NET, generano per il corpo del metodo Construct lo stesso codice IL:
C# |
VB.NET |
class ObjectFactory<T> where T : class, new() { public T Construct() { return new T(); } } |
Class ObjectFactory(Of T As New) Public Function Construct() As T Return New T End Function End Class |
Il codice IL corrispondente è questo:
.maxstack 1
.locals init (!T V_0)
call !!0 [mscorlib]System.Activator::CreateInstance<!T>()
stloc.0
br.s label1
label1: ldloc.0
ret
dove si nota la chiamata al System.Activator.CreateInstance<T>(). Se T è un reference type (vedi la constraint class), questa chiamata al CreateInstance ci sta benissimo, ma se T fosse un value type (lo snippet VB.NET sopra lo permeterebbe!) non avrebbe senso come performance. Ed ecco che il compilatore C#, togliendo via la constraint class, genera il seguente codice IL:
.maxstack 2
.locals init (!T V_0, !T V_1)
nop
ldloca.s V_1
initobj !T
ldloc.1
box !T
brfalse.s label1
ldloca.s V_1
initobj !T
ldloc.1
br.s label2
label1: call !!0 [mscorlib]System.Activator::CreateInstance<!T>()
label2: stloc.0
br.s label3
label3: ldloc.0
ret
che, nel caso di un type parameter value type, istanzia tramite initobj anziché CreateInstance. Quello che sembrava a uno sguardo superficiale un overhead, si mostra essere in realtà un'ottimizzazione! E allora perché non possiamo avere struct e new() insieme? Secondo me, perché i compilatori in discussione (C# e VB.NET) non permettono di definire costruttori default per le struct. Il fatto che per una struct Foo possiamo scrivere new Foo(), non vuol dire che così chiamiamo il costruttore senza parametri della Foo ma, semplicemente che inizializziamo la Foo, ovvero non dobbiamo confondere una questione di sintassi con una questione di semantica.
Per l'ennesima volta, il compilatore C# si mostra, IMHO, più saggio.