¿Cómo puedo obtener un tipo genérico a partir de una representación de cadena?

79

Tengo MyClass<T>.

Y luego tengo esto string s = "MyClass<AnotherClass>";. ¿Cómo puedo obtener Type de la cadena s?

Una forma (fea) es analizar "<" y ">" y hacer:

Type acType = Type.GetType("AnotherClass");  
Type whatIwant = typeof (MyClass<>).MakeGenericType(acType);

Pero, ¿hay una forma más limpia de obtener el tipo final sin analizar, etc.?

DeeStackOverflow
fuente

Respuestas:

94

El formato de los genéricos es el nombre, un carácter `, el número de parámetros de tipo, seguido de una lista delimitada por comas de los tipos entre paréntesis:

Type.GetType("System.Collections.Generic.IEnumerable`1[System.String]");

No estoy seguro de que haya una manera fácil de convertir de la sintaxis C # para genéricos al tipo de cadena que CLR quiere. Comencé a escribir una expresión regular rápida para analizarlo como mencionaste en la pregunta, pero me di cuenta de que a menos que renuncies a la capacidad de tener genéricos anidados como parámetros de tipo, el análisis se volverá muy complicado.

Neil Williams
fuente
+1 - gran respuesta, ¡gracias! ¡Estaba jugueteando tratando de averiguar cómo manejar los genéricos!
marc_s
Gracias. Esto funciona y tengo que modificar el código para formatear la cadena de esta manera. Sin embargo, me preguntaba si todavía hay una manera de usar simplemente: "MyClass <AnotherClass>" exactamente como se muestra en la cadena para obtener la instancia de Type. Parece mucho más limpio.
DeeStackOverflow
No tiene que modificar el código para formatear la cadena de esta manera, simplemente llame a ToString () en el Tipo.
Rush Frisby
38

Mira Activator.CreateInstance, puedes llamarlo con un tipo

Activator.CreateInstance(typeof(MyType))

o con un ensamblado y un nombre de tipo como string

Activator.CreateInstance("myAssembly", "myType")

Esto le dará una instancia del tipo que necesita.

Si necesitas el Type lugar de la instancia, use el Type.GetType()método y el nombre completo del tipo que le interesa, por ejemplo:

string s = "System.Text.StringBuilder";
Type myClassType = Type.GetType(s);

Eso te dará la Typepregunta en cuestión.

marc_s
fuente
3
Esto solo obtiene una instancia del tipo, no una instancia System.Type, que según el fragmento de código, parece ser lo que OP está buscando.
Daniel Schaffer
26

Necesitaba algo como esto y terminé escribiendo un código para analizar los nombres de tipos simples que necesitaba. Por supuesto, hay margen de mejora, ya que no identifica los nombres de tipos genéricos como List<string>, pero lo hace muy bien para string, int[], decimal?y tal. Compartir en caso de que esto ayude a alguien.

public static class TypeExtensions
{
  public static Type GetTypeFromSimpleName(string typeName)
  {
    if (typeName == null)
      throw new ArgumentNullException("typeName");

    bool isArray = false, isNullable = false;

    if (typeName.IndexOf("[]") != -1)
    {
      isArray = true;
      typeName = typeName.Remove(typeName.IndexOf("[]"), 2);
    }

    if (typeName.IndexOf("?") != -1)
    {
      isNullable = true;
      typeName = typeName.Remove(typeName.IndexOf("?"), 1);
    }

    typeName = typeName.ToLower();

    string parsedTypeName = null;
    switch (typeName)
    {
      case "bool":
      case "boolean":
        parsedTypeName = "System.Boolean";
        break;
      case "byte":
        parsedTypeName = "System.Byte";
        break;
      case "char":
        parsedTypeName = "System.Char";
        break;
      case "datetime":
        parsedTypeName = "System.DateTime";
        break;
      case "datetimeoffset":
        parsedTypeName = "System.DateTimeOffset";
        break;
      case "decimal":
        parsedTypeName = "System.Decimal";
        break;
      case "double":
        parsedTypeName = "System.Double";
        break;
      case "float":
        parsedTypeName = "System.Single";
        break;
      case "int16":
      case "short":
        parsedTypeName = "System.Int16";
        break;
      case "int32":
      case "int":
        parsedTypeName = "System.Int32";
        break;
      case "int64":
      case "long":
        parsedTypeName = "System.Int64";
        break;
      case "object":
        parsedTypeName = "System.Object";
        break;
      case "sbyte":
        parsedTypeName = "System.SByte";
        break;
      case "string":
        parsedTypeName = "System.String";
        break;
      case "timespan":
        parsedTypeName = "System.TimeSpan";
        break;
      case "uint16":
      case "ushort":
        parsedTypeName = "System.UInt16";
        break;
      case "uint32":
      case "uint":
        parsedTypeName = "System.UInt32";
        break;
      case "uint64":
      case "ulong":
        parsedTypeName = "System.UInt64";
        break;
    }

    if (parsedTypeName != null)
    {
      if (isArray)
        parsedTypeName = parsedTypeName + "[]";

      if (isNullable)
        parsedTypeName = String.Concat("System.Nullable`1[", parsedTypeName, "]");
    }
    else
      parsedTypeName = typeName;

    // Expected to throw an exception in case the type has not been recognized.
    return Type.GetType(parsedTypeName);
  }
}

Usarlo es tan simple como escribir esto:

Type t;

t = TypeExtensions.GetTypeFromSimpleName("string");
t = TypeExtensions.GetTypeFromSimpleName("int[]");
t = TypeExtensions.GetTypeFromSimpleName("decimal?");
Phillippe Santana
fuente
1
¡Corto, perfecto, extremadamente útil! Gracias
xrnd
3

Para obtener el objeto de tipo de la cadena, use:

Type mytype = Type.GetType(typeName);

Luego puede pasar esto a Activator.CreateInstance():

Activator.CreateInstance(mytype);
Turnor
fuente
0

No tengo mucho tiempo para analizar esto, aunque creo que he visto algunas respuestas similares. En particular, creo que están haciendo exactamente lo que quieres hacer aquí:

Error de repositorio genérico de Entity Framework

(String.Format("[{0}]", baseType.Name.ToString())).OfType<T>();

Espero que esto ayude, avíseme más específicamente si esto no es así.

Jeff Ancel
fuente