2009-04-16 10 views
9

Tengo una utilidad de serialización de cadenas que toma una variable de (casi) cualquier tipo y la convierte en una cadena. Así, por ejemplo, según mi convención, un valor entero de 123 se serializaría como "i: 3: 123" (i = entero, 3 = longitud de cadena; 123 = valor).¿Cómo puedo detectar que un objeto es una colección genérica y qué tipos contiene?

La utilidad maneja todo tipo primitivo, así como algunas colecciones no genéricas, como ArrayLists y Hashtables. La interfaz es de la forma

public static string StringSerialize(object o) {}

e internamente detecto lo que escriba el objeto es serializar y que en consecuencia.

Ahora quiero actualizar mi utilidad para manejar colecciones genéricas. Lo curioso es que no puedo encontrar una función apropiada para detectar que el objeto es una colección genérica, y qué tipos contiene: las dos piezas de información que necesito para serializarla correctamente. Hasta la fecha he estado utilizando la codificación de la forma

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

pero eso no parece funcionar con los genéricos.

¿Qué me recomiendas?


EDIT: Gracias a Lucero, he llegado más cerca de la respuesta, pero estoy atascado en este pequeño enigma sintáctica aquí:

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

Este código no compila, porque " lt "no está permitido como el argumento <T> de un objeto List<>. Por qué no? ¿Y cuál es la sintaxis correcta?

+0

No se puede usar lt porque es información del tipo de tiempo de ejecución, no de compilación ... ¿qué genéricos se utilizan? ¿Por qué incluso se desea crear una lista genérica de esta manera? parece muy sin sentido. – meandmycode

+0

http://stackoverflow.com/questions/266115/pass-an-instantiated-systemtype-as-a-type-parameter-for-a-generic-class - así es como necesitas hacer esto si REALMENTE quieres ir de esta manera, pero para mí creo que puede estar perdiendo el sentido de los genéricos. – meandmycode

+0

@meandmycode - es perfectamente normal para el código de la utilidad de serialización. –

Respuesta

5

Re su enigma; Supongo que stringifyList es un método genérico? Usted tendría que invocar con la reflexión:

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

donde {target} es null para un método estático, o this para un método de instancia en la instancia actual.

Además - No asumiría que todas las colecciones son a: basadas en List<T>, b: tipos genéricos. Lo importante es: ¿Implementan IList<T> para algunos T?

Aquí está un ejemplo 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

Gracias Marc! Pero ayúdame a comprender, por favor, ¿por qué tenemos que recurrir a la reflexión? ¿No hay alguna forma de llamar a StringifyList que comprenda el compilador? ¿Por qué MS no le permite declarar una Lista <> del tipo Lista ? –

+0

No hay tal manera. MakeGenericMethod y MakeGenericType son las únicas opciones si desea utilizar genéricos con tipos resueltos en tiempo de ejecución. –

+0

Hummm. "Las operaciones limitadas tardías no se pueden realizar en los tipos o métodos para los que ContainsGenericParameters es verdadero". ¿Obtuviste tu código para ejecutar? –

7

Use el tipo para recopilar la información requerida.

Para objetos genéricos, llame a GetType() para obtener su tipo y luego marque IsGenericType para averiguar si es genérico. Si es así, puede obtener la definición de tipo genérico, que se puede comparar, por ejemplo, así: typeof(List<>)==yourType.GetGenericTypeDefinition(). Para saber cuáles son los tipos genéricos, use el método GetGenericArguments, que devolverá una matriz de los tipos utilizados.

Para comparar tipos, puede hacer lo siguiente: if (typeof(int).IsAssignableFrom(yourGenericTypeArgument)).


EDIT para responder seguimiento:

Simplemente haga su método stringifyList aceptar una IEnumerable (no genérico) como parámetro y quizá también el conocido argumento de tipo genérico, y usted estará bien; a continuación, puede usar foreach para revisar todos los elementos y manejarlos según el argumento de tipo si es necesario.

+0

Buena respuesta, gracias, pero me falta un pequeño truco sintáctico: ¿cómo echo desde el objeto o a la Lista ? –

+1

Suponiendo que todas las colecciones son a: genéricas, o b: relacionadas con la lista es arriesgado. Podría escribir mi propio FooCollection: IList que no es ninguno de los dos. –

+0

Realmente no necesitas lanzar esto, solo usa el IEnumerable no genérico (por ejemplo, haz un foreach en la clase). O bien, si verificó que el argumento de tipo genérico coincida, puede, por supuesto, hacer un lanzamiento difícil, p. IList cuando el ejemplo IsAssignableFrom anterior devuelve verdadero. – Lucero

1

En el nivel más básico, todas las listas genéricas implementan IEnumerable<T>, que es en sí mismo un descendiente de IEnumerable. Si desea serializar una lista, simplemente puede soltarla en IEnumerable y enumerar los objetos genéricos dentro de ellos.

La razón por la cual no se puede hacer

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

se debe a que los genéricos todavía tienen que ser estáticamente fuerte con tipo, y lo que estamos tratando de hacer es crear un tipo dinámico. List<string> y List<int>, a pesar de utilizar la misma interfaz genérica, son dos tipos completamente distintos, y no se puede convertir entre ellos.

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

¿qué tipo recibiría stringifyList(x)? La interfaz más básica que puede pasar aquí es IEnumerable, ya que IList<T> no hereda de IList.

Para serializar la lista genérica, debe conservar la información del tipo original de la lista para que pueda volver a crear con Activator. Si desea optimizar un poco para que no tenga que verificar el tipo de cada miembro de la lista en su método de stringify, puede pasar el Tipo que ha extraído de la lista directamente.

Cuestiones relacionadas