Domanda

Esaminiamo il codice MSIL generato per il seguente metodo generico:

public static U BoxValue<T, U>(T value)
  where T : struct, U
  where U : class
{
  return value;
}

Aspetto:

.method public hidebysig static !!U  BoxValue<valuetype .ctor
 ([mscorlib]System.ValueType, !!U) T,class U>(!!T 'value') cil managed
{
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  box        !!T
  IL_0006:  unbox.any  !!U
  IL_000b:  ret
}

Ma per il codice generico di cui sopra, la rappresentazione IL più efficiente dovrebbe essere:

  IL_0000:  ldarg.0
  IL_0001:  box        !!T
  IL_0006:  ret

È noto dai vincoli in cui il valore è inscatolato tipologia di riferimento. Unbox.any opcode è completamente ridondante perché after box opcode il valore nello stack IL sarà già un riferimento valido !!U, che può essere utilizzato senza alcun unboxing.

Perché il compilatore C# 3.0 non utilizza i metadati dei vincoli per emettere codice generico più efficiente?Unbox.any comporta un piccolo sovraccarico (solo 4x-5 volte più lento), ma perché non generare un codice migliore in questo scenario?

È stato utile?

Soluzione

Sembra che il compilatore lo faccia a causa di alcuni problemi con il verificatore.

L'IL che si desidera venga generato dal compilatore non è verificabile e quindi il compilatore C# non può generarlo (tutto il codice C# al di fuori dei contesti "non sicuri" dovrebbe essere verificabile).

Le regole per la "compatibilità del tipo di verifica" sono fornite nella Sezione 1.8.1.2.3, Parte III delle specifiche Ecma.

Dicono che un tipo 'S' è una verifica compatibile con un tipo 'T' o (S:= T) utilizzando le seguenti regole:

  1. [:= è riflessivo] Per tutti i tipi di verifica S, S := S
  2. [:= è transitivo] Per tutti i tipi di verifica S, T e U se S := T e T := U, allora S := U.
  3. S := T se S è la classe base di T o un'interfaccia implementata da T e T non è un tipo valore.
  4. oggetto := T se T è un tipo di interfaccia.
  5. S: = t se s e t sono entrambe interfacce e l'implementazione di t richiede l'implementazione di s
  6. S := null se S è un tipo di oggetto o un'interfaccia
  7. S []: = T [] se S: ​​= T e gli array sono entrambi vettori (zero a base di zero) o nessuno dei due è un vettore ed entrambi hanno lo stesso rango.(Questa regola riguarda la covarianza dell'array.)
  8. Se S e T sono puntatori del metodo, allora S: = T se le firme (tipi di ritorno, tipi di parametri e convenzione chiamante) sono uguali.

Di queste regole, l'unica che potrebbe essere applicabile in questo caso è la n. 3.

Tuttavia, il punto 3 non si applica al tuo codice, perché "U" non è una classe base di "T" e non è un'interfaccia base di "T", quindi il controllo "o" restituisce falso.

Ciò significa che ALCUNE istruzioni devono essere eseguite per convertire una T inscatolata in una U in modo da superare il verificatore.

Sarei d'accordo con te sul fatto che le regole di verifica dovrebbero essere modificate, in modo che la generazione del codice desiderato sia effettivamente verificabile.

Tecnicamente, tuttavia, il compilatore sta facendo la cosa "corretta" in base alle specifiche ECMA.

Dovresti segnalare un bug a qualcuno di Microsoft.

Altri suggerimenti

Questi vincoli sembrano strani:

where T : struct, U
where U : class

T è un tipo di valore, ma nello stesso tempo deve ereditare da U che è un tipo di riferimento. Mi chiedo che tipo potrebbero soddisfare i vincoli di cui sopra e ci permettono di chiamare questo metodo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top