2009-04-16 6 views
9

Ho un'utilità di serializzazione delle stringhe che accetta una variabile di (quasi) qualsiasi tipo e la converte in una stringa. Quindi, ad esempio, secondo la mia convenzione, un valore intero di 123 sarebbe serializzato come "i: 3: 123" (i = intero; 3 = lunghezza della stringa; 123 = valore).Come faccio a sapere se un oggetto è una collezione generica e quali tipi contiene?

L'utilità gestisce tutti i tipi primitivi, nonché alcune raccolte non generiche, come ArrayList e Hashtables. L'interfaccia è di forma

public static string StringSerialize(object o) {}

internamente io rilevare che tipo è l'oggetto e serializzare conseguenza.

Ora voglio aggiornare la mia utilità per gestire raccolte generiche. La cosa divertente è che non riesco a trovare una funzione appropriata per rilevare che l'oggetto è una raccolta generica e che tipi contiene: entrambe le informazioni necessarie per la serializzazione corretta. Fino ad oggi ho usato la codifica della forma

if (o is int) {// do something}

ma questo non sembra funzionare con i generici.

Che cosa mi consiglia?


EDIT: Grazie a Lucero, ho ottenuto più vicino alla risposta, ma mi sono bloccato in questo piccolo enigma sintattica qui:

if (t.IsGenericType) { 
    if (typeof(List<>) == t.GetGenericTypeDefinition()) { 
    Type lt = t.GetGenericArguments()[0]; 
    List<lt> x = (List<lt>)o; 
    stringifyList(x); 
    } 
} 

Questo codice non si compila, perché " lt "non è consentito come argomento <T> di un oggetto List<>. Perchè no? E qual è la sintassi corretta?

+0

Non puoi usare perché le sue informazioni sul tipo di runtime, non compilare il tempo .. (che usi generici), perché vuoi anche creare un elenco generico in questo modo comunque? sembra molto inutile. – meandmycode

+0

http://stackoverflow.com/questions/266115/pass-an-instantiated-systemtype-as-a-type-parameter-for-a-generic-class - ecco come devi fare questo se davvero vuoi andare in questo modo, ma secondo me potrebbe mancare il punto di generici. – meandmycode

+0

@meandmycode: è perfettamente normale per il codice di utilità di serializzazione. –

risposta

5

Re il tuo enigma; Sto assumendo che stringifyList sia un metodo generico? Si avrebbe bisogno di invocarlo con la riflessione:

MethodInfo method = typeof(SomeType).GetMethod("stringifyList") 
      .MakeGenericMethod(lt).Invoke({target}, new object[] {o}); 

dove {target} è null per un metodo statico, o this per un metodo di istanza per l'istanza corrente.

Inoltre, non assumerei che tutte le raccolte siano a: basate su List<T>, b: tipi generici. L'importante è: implementano IList<T> per alcuni T?

Ecco un esempio completo:

using System; 
using System.Collections.Generic; 
static class Program { 
    static Type GetListType(Type type) { 
     foreach (Type intType in type.GetInterfaces()) { 
      if (intType.IsGenericType 
       && intType.GetGenericTypeDefinition() == typeof(IList<>)) { 
       return intType.GetGenericArguments()[0]; 
      } 
     } 
     return null; 
    } 
    static void Main() { 
     object o = new List<int> { 1, 2, 3, 4, 5 }; 
     Type t = o.GetType(); 
     Type lt = GetListType(t); 
     if (lt != null) { 
      typeof(Program).GetMethod("StringifyList") 
       .MakeGenericMethod(lt).Invoke(null, 
       new object[] { o }); 
     } 
    } 
    public static void StringifyList<T>(IList<T> list) { 
     Console.WriteLine("Working with " + typeof(T).Name); 
    } 
} 
+0

Grazie Marc! Ma aiutami a capire per favore - perché dobbiamo ricorrere alla riflessione? Non c'è un modo per chiamare StringifyList che il compilatore capirà? Perché MS non consente di dichiarare un elenco <> di tipo Elenco ? –

+0

Non esiste un modo. MakeGenericMethod e MakeGenericType sono le uniche opzioni se si desidera utilizzare i generici con tipi risolti in fase di runtime. –

+0

Hummm. "Le operazioni tardive non possono essere eseguite su tipi o metodi per i quali ContainsGenericParameters è true." Hai ottenuto il tuo codice per l'esecuzione? –

7

Utilizzare il tipo per raccogliere le informazioni richieste.

Per oggetti generici, chiamare GetType() per ottenere il loro tipo e quindi controllare IsGenericType per scoprire se è generico. In tal caso, è possibile ottenere la definizione del tipo generico, che può essere confrontato ad esempio in questo modo: typeof(List<>)==yourType.GetGenericTypeDefinition(). Per scoprire quali sono i tipi generici, utilizzare il metodo GetGenericArguments, che restituirà un array dei tipi utilizzati.

Per confrontare i tipi, è possibile effettuare le seguenti operazioni: if (typeof(int).IsAssignableFrom(yourGenericTypeArgument)).


EDIT per rispondere followup:

Basta fare il tuo metodo di stringifyList accettare un IEnumerable (non generici) come parametro e forse anche il noto argomento di tipo generico, e si andrà tutto bene; è quindi possibile utilizzare foreach per esaminare tutti gli elementi e gestirli in base all'argomento del tipo, se necessario.

+0

Buona risposta, grazie - ma mi manca solo un piccolo trucco sintattico: come faccio a trasmettere dall'oggetto o alla lista ? –

+1

Supponendo che tutte le raccolte siano a: generico o b: correlato a Elenco è rischioso. Potrei scrivere la mia FooCollection: IList che non è né l'uno né l'altro. –

+0

Non hai davvero bisogno di lanciare questo, basta usare l'IEnumerable non generico (ad esempio, basta fare un foreach sulla classe). Oppure, se hai controllato che l'argomento del tipo generico corrisponda, puoi ovviamente anche fare un cast duro, ad es. IList quando l'esempio IsAssignableFrom riportato sopra è true. – Lucero

1

Al livello più fondamentale, tutti gli elenchi generici implementano IEnumerable<T>, che è di per sé un discendente di IEnumerable. Se si desidera serializzare un elenco, è possibile semplicemente scenderlo in IEnumerable e enumerare gli oggetti generici al loro interno.

Il motivo per cui non si può fare

Type lt = t.GetGenericArguments()[0]; 
List<lt> x = (List<lt>)o; 
stringifyList(x); 

è perché i generici hanno ancora bisogno di essere staticamente tipizzato forte, e ciò che si sta cercando di fare è quello di creare un tipo dinamico. List<string> e List<int>, nonostante utilizzino la stessa interfaccia generica, sono due tipi completamente distinti e non è possibile effettuare il cast tra di essi.

List<int> intList = new List<int>(); 
List<string> strList = intList; // error! 

Quale tipo riceverà stringifyList(x)? L'interfaccia più semplice che puoi passare qui è IEnumerable, poiché IList<T> non eredita da IList.

Per serializzare l'elenco generico, è necessario conservare le informazioni sul Tipo originale dell'elenco in modo da poter ricreare con Activator. Se si desidera ottimizzare leggermente in modo da non dover controllare il tipo di ciascun membro dell'elenco nel metodo stringify, è possibile passare direttamente il Tipo estratto dall'elenco.