2012-03-29 8 views
61

Ho la seguente funzione:Perché la trasmissione di una dinamica di tipo oggetto a oggetto genera un'eccezione di riferimento null?

public static T TryGetArrayValue<T>(object[] array_, int index_) 
{ 
    ... //some checking goes up here not relevant to question 

    dynamic boxed = array_[index_]; 
    return (T)boxed; 
} 

Quando chiamo nel seguente modo,

object a = new object(); 
object v = TUtils.TryGetArrayValue<object>(new object[] { a }, 0); 

(T)boxed genera un'eccezione riferimento nullo.

Qualsiasi altro tipo che ho messo lì oltre che "oggetto", funziona perfettamente bene.
Qualche idea di cosa si tratta e perché si sta generando un'eccezione?

Edit: Il motivo per cui io uso dinamico è quello di evitare un'eccezione durante la conversione tipi, per esempio:

double a = 123; 
int v = TUtils.TryGetArrayValue<int>(new object[] { a }, 0); 
+0

Controllare per vedere se 'boxed' è null prima del casting. –

+5

Questo probabilmente un bug nel modo in cui 'dynamic' è implementato. Controllando se questa repros in 4.5 ora. – dlev

+1

Repro - e sembra un bug nel modo in cui viene gestito 'dynamic' - la domanda è valida – BrokenGlass

risposta

41

Sono d'accordo con gli altri answerers che dicono che questo appare come un insetto. In particolare sembra che ci sia un bug nel livello di binding del runtime C#, anche se non l'ho studiato a fondo.

Mi scuso per l'errore. Lo segnalerò al team di test C# 5 e vedremo se è già stato segnalato e risolto in C# 5. (Si riproduce nella recente versione beta, quindi è improbabile che sia già stato segnalato e risolto. In caso contrario, è improbabile che una correzione arrivi alla versione finale. In tal caso lo considereremo per una possibile versione di manutenzione.

Grazie per aver portato questo alla nostra attenzione. Se ti senti come entering a Connect issue tracciarlo, sentiti libero di farlo e ti preghiamo di includere un link a questa domanda StackOverflow. Se non lo fai, nessun problema; il team di test lo saprà in ogni caso.

+1

Posso ottenere una menzione d'onore per aver scoperto il bug? : D – bedo

+28

@bedo: assolutamente. Dieci punti a Grifondoro! –

+0

Ho appena segnato un'altra domanda come duplicato di questo. È stato segnalato un bug a Connect? In tal caso, esiste un link in cui è possibile tenere traccia dello stato? –

2

Ha qualcosa a che fare con la parola chiave dinamica. Se cambio il tipo in T per scatola funziona.

static void Main(string[] args) 
    { 
     object a = new object(); 
     object v = TryGetArrayValue<object>(new object[] { a }, 0); 

     Console.ReadLine(); 
    } 

    public static T TryGetArrayValue<T>(object[] array_, int index_) 
    { 

      T boxed = (T)array_[index_]; 
      return boxed; 

    } 

C'è un motivo particolare che si sta utilizzando dinamico? In questo caso non ne hai davvero bisogno, perché sai cosa è in anticipo il tipo. Se si guarda, nella propria versione il tipo di box non è un oggetto ma è dinamico {object} che potrebbe essere il problema quando si tenta di eseguire il cast sull'oggetto. Se guardi questa versione che ho postato, ottieni un tipo di oggetto e nessun errore.

+0

Sì, ho un caso d'uso per l'utilizzo dinamico, perché voglio fare qualcosa di simile: doppio a = 10923049; int v = TUtils.TryGetArrayValue (nuovo oggetto [] {a}, 0); utilizzo dinamico mi aiuta a eseguire il cast da un diverso tipo compatibile. Altrimenti lancia un'eccezione. – bedo

+0

@bedo con un metodo chiamato TryGetArrayValue, mi aspetto che dovrebbe riuscire a fallire. Vedere la tua intenzione mi fa solo rabbrividire! Anche se sono un grande fan della tipizzazione statica e del tipo di compilazione. – Jetti

6

Questo comportamento è davvero strano e in effetti sembra un bug nell'implementazione di dynamic. Ho scoperto che questa variazione non un'eccezione e infatti restituisce l'oggetto:

public static T TryGetArrayValue<T>(object[] array, int index) where T : class 
{ 
    dynamic boxed = array[index]; 
    return boxed as T; 
} 

Si noti che ho dovuto aggiungere un vincolo generico nella firma del metodo perché l'operatore as funziona solo se T è un tipo di riferimento.

Se siete alla ricerca di una soluzione alternativa, è possibile utilizzare questo (e so che è brutto):

public static T TryGetArrayValue<T>(object[] array, int index) 
{ 
    dynamic boxed = array[index]; 

    if (typeof(T) == typeof(object)) 
     return (T)(boxed as object); 

    return (T)boxed; 
} 
+0

Purtroppo ho bisogno che funzioni anche per i primitivi. Forse posso semplicemente gestire l'oggetto come un caso speciale e non trasmettere quando ritorno. – bedo

+0

Questo non viene nemmeno compilato se si sta tentando di utilizzare 'TryGetArrayValue (...) – Jetti

+0

@bedo - Ho esteso la mia risposta per mostrare come è possibile aggirare questo problema utilizzando la parola chiave' as'. –

14

questo è un problema con il modo dinamico opere - il legante runtime ha un problema con conversioni da System.Object , ma in pratica, non è davvero un problema.

ho il sospetto che questo è perché dynamic, in fase di esecuzione, è di per sé sempre System.Object. La specifica del linguaggio C# in 4.7 afferma: "Il tipo dinamico non è distinguibile dall'oggetto in fase di esecuzione." In quanto tale, qualsiasi oggetto usato come dinamico viene semplicemente memorizzato come oggetto.

Quando si inserisce un'istanza effettiva di System.Object in una dinamica, si verifica qualcosa all'interno della risoluzione di associazione del runtime che causa un'eccezione di riferimento null.

Tuttavia, qualsiasi altro tipo che non è System.Object funziona - anche tipi di riferimento e simili, senza difetti. In quanto tale, questo dovrebbe fornire il comportamento corretto, poiché non ci sono davvero motivi per creare un'istanza di System.Object che dovrebbe essere passata in giro - si vorrebbe sempre qualche sottoclasse con altre informazioni di tipo.

Non appena si utilizza un tipo "reale", questo funziona correttamente. Per exmaple, i seguenti lavori, anche se è passato e trattato come Object:

public class Program 
{ 
    public static T TryGetArrayValue<T>(object[] array_, int index_) 
    { 

     dynamic boxed = array_[index_]; 
     return (T)boxed; 
    } 

    private static void Main() 
    { 
     int p = 3; 
     object a = p; 
     var objects = new[] { a, 4.5 }; 

     // This works now, since the object is pointing to a class instance 
     object v = TryGetArrayValue<object>(objects, 0); 
     Console.WriteLine(v); 

     // These both also work fine... 
     double d = TryGetArrayValue<double>(objects, 1); 
     Console.WriteLine(d); 
     // Even the "automatic" int conversion works now 
     int i = TryGetArrayValue<int>(objects, 1); 
     Console.WriteLine(i); 
     Console.ReadKey(); 
    } 
} 
+0

Grande, grazie per la spiegazione! – bedo

+0

@bedo Non riesco a capire perché fallisce con l'oggetto, ma come ho detto, in pratica, non dovrebbe avere importanza;) –

Problemi correlati