2010-03-15 15 views

Respuesta

53

Veo que ya ha aceptado una respuesta, pero sinceramente, esa respuesta no será suficiente para hacer esto de manera confiable si solo combina lo que hay allí con lo que ya escribió. Está en el camino correcto, pero su código solo funcionará para los tipos genéricos con exactamente un parámetro genérico, y solo funcionará cuando el parámetro de tipo genérico en sí mismo no sea genérico.

Esta es una función (escrito como un método de extensión) que en realidad debería funcionar en todos los casos:

public static class TypeExtensions 
{ 
    public static string ToGenericTypeString(this Type t) 
    { 
     if (!t.IsGenericType) 
      return t.Name; 
     string genericTypeName = t.GetGenericTypeDefinition().Name; 
     genericTypeName = genericTypeName.Substring(0, 
      genericTypeName.IndexOf('`')); 
     string genericArgs = string.Join(",", 
      t.GetGenericArguments() 
       .Select(ta => ToGenericTypeString(ta)).ToArray()); 
     return genericTypeName + "<" + genericArgs + ">"; 
    } 
} 

Esta función es recursiva y segura. Si se ejecuta en esta entrada:

Console.WriteLine(
    typeof(Dictionary<string, List<Func<string, bool>>>) 
    .ToGenericTypeString()); 

recibe este (correcto) de salida:

Dictionary<String,List<Func<String,Boolean>>> 
+1

Es una lástima que el CLR no viene con esta función. –

+3

@Paul Ruane: Estoy de acuerdo en que podría ser útil en ciertos casos, sin embargo, el CLR es independiente del idioma, y ​​no habría forma de implementar algo así para trabajar igual de bien en C#, VB.NET, F #, IronPython y todos los demás lenguajes de CLR. El nombre de aspecto extraño con el contragolpe es en realidad el verdadero nombre de tipo CLR; el formato anterior es específico de C#. – Aaronaught

+0

Ah sí, buen punto. ¡Tengo la visión del túnel C#! –

0

pequeña adición a @Aaronaught

public string ToGenericTypeString(Type t) 
{ 
    if (!t.IsGenericType) 
     return t.FullName; 
    string genericTypeName = t.GetGenericTypeDefinition().FullName; 
    genericTypeName = genericTypeName.Substring(0, 
     genericTypeName.IndexOf('`')); 
    string genericArgs = string.Join(",", 
     t.GetGenericArguments() 
      .Select(ta => ToGenericTypeString(ta)).ToArray()); 
    return genericTypeName + "<" + genericArgs + ">"; 
} 
+0

Parece que lo único que ha cambiado es de 'Nombre' a' FullName'? No estoy seguro de si eso está cambiando materialmente lo suficiente como para justificar una respuesta alternativa; probablemente debería haberse publicado como comentario. – Aaronaught

+0

No es una alternativa. Fullname ayuda con la resolución del espacio de nombres. –

5

Mientras que la solución aceptada es bueno para simplemente el nombre o un nombre completo no anidado (reemplazando el nombre por el nombre completo como en la respuesta de @Ose E); sin embargo, para los tipos anidados, aún no funcionará, y tampoco para las matrices de tipos genéricos.

Así que aquí hay una solución que funcionará, (pero tenga en cuenta que esta solución solo establecerá los argumentos reales, solo si se establecen todos los argumentos, es decir, si el tipo de declaración ha proporcionado el tipo arguemts, siempre y cuando el tipo genérico más interno no, aún no aparecerá incluso para la base).

public static string ToGenericTypeString(this Type t, params Type[] arg) 
    { 
     if (t.IsGenericParameter || t.FullName == null) return t.Name;//Generic argument stub 
     bool isGeneric = t.IsGenericType || t.FullName.IndexOf('`') >= 0;//an array of generic types is not considered a generic type although it still have the genetic notation 
     bool isArray = !t.IsGenericType && t.FullName.IndexOf('`') >= 0; 
     Type genericType = t; 
     while (genericType.IsNested && genericType.DeclaringType.GetGenericArguments().Count()==t.GetGenericArguments().Count())//Non generic class in a generic class is also considered in Type as being generic 
     { 
      genericType = genericType.DeclaringType; 
     } 
     if (!isGeneric) return t.FullName.Replace('+', '.'); 

     var arguments = arg.Any() ? arg : t.GetGenericArguments();//if arg has any then we are in the recursive part, note that we always must take arguments from t, since only t (the last one) will actually have the constructed type arguments and all others will just contain the generic parameters 
     string genericTypeName = genericType.FullName; 
     if (genericType.IsNested) 
     { 
      var argumentsToPass = arguments.Take(genericType.DeclaringType.GetGenericArguments().Count()).ToArray();//Only the innermost will return the actual object and only from the GetGenericArguments directly on the type, not on the on genericDfintion, and only when all parameters including of the innermost are set 
      arguments = arguments.Skip(argumentsToPass.Count()).ToArray(); 
      genericTypeName = genericType.DeclaringType.ToGenericTypeString(argumentsToPass) + "." + genericType.Name;//Recursive 
     } 
     if (isArray) 
     { 
      genericTypeName = t.GetElementType().ToGenericTypeString() + "[]";//this should work even for multidimensional arrays 
     } 
     if (genericTypeName.IndexOf('`') >= 0) 
     { 
      genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`')); 
      string genericArgs = string.Join(",", arguments.Select(a => a.ToGenericTypeString()).ToArray()); 
       //Recursive 
      genericTypeName = genericTypeName + "<" + genericArgs + ">"; 
      if (isArray) genericTypeName += "[]"; 
     } 
     if (t != genericType) 
     { 
      genericTypeName += t.FullName.Replace(genericType.FullName, "").Replace('+','.'); 
     } 
     if (genericTypeName.IndexOf("[") >= 0 && genericTypeName.IndexOf("]") != genericTypeName.IndexOf("[") +1) genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf("["));//For a non generic class nested in a generic class we will still have the type parameters at the end 
     return genericTypeName; 
    } 
+0

Usé esta solución en un proyecto en el que estoy trabajando, funciona muy bien – Oren

0

Esta es mi solución, sino que también trabaja para clases anidadas y los genéricos:

public static string GenericTypeString(this Type t) 
    { 
     if (!t.IsGenericType) 
     { 
      return t.GetFullNameWithoutNamespace() 
        .ReplacePlusWithDotInNestedTypeName(); 
     } 

     return t.GetGenericTypeDefinition() 
       .GetFullNameWithoutNamespace() 
       .ReplacePlusWithDotInNestedTypeName() 
       .ReplaceGenericParametersInGenericTypeName(t); 
    } 

    private static string GetFullNameWithoutNamespace(this Type type) 
    { 
     if (type.IsGenericParameter) 
     { 
      return type.Name; 
     } 

     const int dotLength = 1; 
     return type.FullName.Substring(type.Namespace.Length + dotLength); 
    } 

    private static string ReplacePlusWithDotInNestedTypeName(this string typeName) 
    { 
     return typeName.Replace('+', '.'); 
    } 

    private static string ReplaceGenericParametersInGenericTypeName(this string typeName, Type t) 
    { 
     var genericArguments = t.GetGenericArguments(); 

     const string regexForGenericArguments = @"`[1-9]\d*"; 

     var rgx = new Regex(regexForGenericArguments); 

     typeName = rgx.Replace(typeName, match => 
     { 
      var currentGenericArgumentNumbers = int.Parse(match.Value.Substring(1)); 
      var currentArguments = string.Join(",", genericArguments.Take(currentGenericArgumentNumbers).Select(GenericTypeString)); 
      genericArguments = genericArguments.Skip(currentGenericArgumentNumbers).ToArray(); 
      return string.Concat("<", currentArguments, ">"); 
     }); 

     return typeName; 
    } 
0

Esto resultará en exactamente el mismo resultado código como el generador de código de CS. He mejorado el código de yoel halb.

/// <summary> 
    ///  Gets the CS Type Code for a type 
    /// </summary> 
    /// <param name="type">The type.</param> 
    /// <returns></returns> 
    /// <exception cref="System.ArgumentNullException">type</exception> 
    public static string GetCSTypeName(this Type type) 
    { 
     if (type == typeof(string)) 
     { 
      return "string"; 
     } 
     else if (type == typeof(object)) { return "object"; } 
     else if (type == typeof(bool)) { return "bool"; } 
     else if (type == typeof(char)) { return "char"; } 
     else if (type == typeof(int)) { return "int"; } 
     else if (type == typeof(float)) { return "float"; } 
     else if (type == typeof(double)) { return "double"; } 
     else if (type == typeof(long)) { return "long"; } 
     else if (type == typeof(ulong)) { return "ulong"; } 
     else if (type == typeof(uint)) { return "uint"; } 
     else if (type == typeof(byte)) { return "byte"; } 
     else if (type == typeof(Int64)) { return "Int64"; } 
     else if (type == typeof(short)) { return "short"; } 
     else if (type == typeof(decimal)) { return "decimal"; } 
     else if (type.IsGenericType) 
     { 
      return $"{ToGenericTypeString(type)}"; 
     } 
     else if (type.IsArray) 
     { 
      List<string> arrayLength = new List<string>(); 
      for (int i = 0; i < type.GetArrayRank(); i++) 
      { 
       arrayLength.Add("[]"); 
      } 
      return GetCSTypeName(type.GetElementType()) + string.Join("", arrayLength).Replace("+", "."); 
     } 
     else 
     { 
      return type.FullName.Replace("+", "."); 
     } 
    } 

    private static string ToCSReservatedWord(this Type type, bool fullName) 
    { 
     if (type == typeof(string)) 
     { 
      return "string"; 
     } 
     else if (type == typeof(object)) { return "object"; } 
     else if (type == typeof(bool)) { return "bool"; } 
     else if (type == typeof(char)) { return "char"; } 
     else if (type == typeof(int)) { return "int"; } 
     else if (type == typeof(float)) { return "float"; } 
     else if (type == typeof(double)) { return "double"; } 
     else if (type == typeof(long)) { return "long"; } 
     else if (type == typeof(ulong)) { return "ulong"; } 
     else if (type == typeof(uint)) { return "uint"; } 
     else if (type == typeof(byte)) { return "byte"; } 
     else if (type == typeof(Int64)) { return "Int64"; } 
     else if (type == typeof(short)) { return "short"; } 
     else if (type == typeof(decimal)) { return "decimal"; } 
     else 
     { 
      if (fullName) 
      { 
       return type.FullName; 
      } 
      else 
      { 
       return type.Name; 
      } 

     } 
    } 

    public static string ToGenericTypeString(this Type t, params Type[] arg) 
    { 
     if (t.IsGenericParameter || t.FullName == null) return t.FullName;//Generic argument stub 
     bool isGeneric = t.IsGenericType || t.FullName.IndexOf('`') >= 0;//an array of generic types is not considered a generic type although it still have the genetic notation 
     bool isArray = !t.IsGenericType && t.FullName.IndexOf('`') >= 0; 
     Type genericType = t; 
     while (genericType.IsNested && genericType.DeclaringType.GetGenericArguments().Count() == t.GetGenericArguments().Count())//Non generic class in a generic class is also considered in Type as being generic 
     { 
      genericType = genericType.DeclaringType; 
     } 
     if (!isGeneric) return ToCSReservatedWord(t, true).Replace('+', '.'); 

     var arguments = arg.Any() ? arg : t.GetGenericArguments();//if arg has any then we are in the recursive part, note that we always must take arguments from t, since only t (the last one) will actually have the constructed type arguments and all others will just contain the generic parameters 
     string genericTypeName = genericType.ToCSReservatedWord(true); 
     if (genericType.IsNested) 
     { 
      var argumentsToPass = arguments.Take(genericType.DeclaringType.GetGenericArguments().Count()).ToArray();//Only the innermost will return the actual object and only from the GetGenericArguments directly on the type, not on the on genericDfintion, and only when all parameters including of the innermost are set 
      arguments = arguments.Skip(argumentsToPass.Count()).ToArray(); 
      genericTypeName = genericType.DeclaringType.ToGenericTypeString(argumentsToPass) + "." + ToCSReservatedWord(genericType, false);//Recursive 
     } 
     if (isArray) 
     { 
      genericTypeName = t.GetElementType().ToGenericTypeString() + "[]";//this should work even for multidimensional arrays 
     } 
     if (genericTypeName.IndexOf('`') >= 0) 
     { 
      genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`')); 
      string genericArgs = string.Join(", ", arguments.Select(a => a.ToGenericTypeString()).ToArray()); 
      //Recursive 
      genericTypeName = genericTypeName + "<" + genericArgs + ">"; 
      if (isArray) genericTypeName += "[]"; 
     } 
     if (t != genericType) 
     { 
      genericTypeName += t.FullName.Replace(genericType.ToCSReservatedWord(true), "").Replace('+', '.'); 
     } 
     if (genericTypeName.IndexOf("[") >= 0 && genericTypeName.IndexOf("]") != genericTypeName.IndexOf("[") + 1) genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf("["));//For a non generic class nested in a generic class we will still have the type parameters at the end 
     return genericTypeName; 
    } 

esto pasará la siguiente prueba unitaria como se esperaba.

[TestClass] 
public class GetCSName 
{ 

    private string GetCSCompilerName(Type type) 
    { 
     if (type == null) 
     { 
      throw new ArgumentNullException(nameof(type)); 
     } 
     var compiler = new CSharpCodeProvider(); 
     var typeRef = new CodeTypeReference(type); 
     return compiler.GetTypeOutput(typeRef); 
    } 

    [TestMethod] 
    public void TestMethod1() 
    { 
     List<Type> typesToTest = new List<Type>(); 
     typesToTest.Add(typeof(string)); 
     typesToTest.Add(typeof(string[])); 
     typesToTest.Add(typeof(object[])); 
     typesToTest.Add(typeof(bool[])); 
     typesToTest.Add(typeof(string)); 
     typesToTest.Add(typeof(object)); 
     typesToTest.Add(typeof(int)); 
     typesToTest.Add(typeof(double)); 
     typesToTest.Add(typeof(float)); 
     typesToTest.Add(typeof(bool)); 
     typesToTest.Add(typeof(char)); 
     typesToTest.Add(typeof(decimal)); 
     typesToTest.Add(typeof(decimal?[])); 
     typesToTest.Add(typeof(decimal?[][])); 
     typesToTest.Add(typeof(Int64)); 
     typesToTest.Add(typeof(Guid)); 
     typesToTest.Add(typeof(int?)); 
     typesToTest.Add(typeof(double?)); 
     typesToTest.Add(typeof(float?)); 
     typesToTest.Add(typeof(bool?)); 
     typesToTest.Add(typeof(char?)); 
     typesToTest.Add(typeof(decimal?)); 
     typesToTest.Add(typeof(Int64?)); 
     typesToTest.Add(typeof(Guid?)); 
     typesToTest.Add(typeof(List<string>)); 
     typesToTest.Add(typeof(Dictionary<string, Guid>)); 
     typesToTest.Add(typeof(Dictionary<string, Guid>[])); 
     typesToTest.Add(typeof(Dictionary<string, Guid?>)); 
     typesToTest.Add(typeof(Dictionary<string, Dictionary<string, Guid?>>)); 
     typesToTest.Add(typeof(Dictionary<string, Dictionary<string, Guid?>>[])); 
     typesToTest.Add(typeof(Dictionary<string, Dictionary<string, Guid?>>[][])); 
     typesToTest.Add(typeof(int[])); 
     typesToTest.Add(typeof(int[][])); 
     typesToTest.Add(typeof(int[][][])); 
     typesToTest.Add(typeof(int[][][][])); 
     typesToTest.Add(typeof(int[][][][][])); 
     typesToTest.Add(typeof(TestClass)); 
     typesToTest.Add(typeof(List<TestClass>)); 
     typesToTest.Add(typeof(Dictionary<TestClass, TestClass>)); 
     typesToTest.Add(typeof(Dictionary<string, TestClass>)); 
     typesToTest.Add(typeof(List<Dictionary<string, TestClass>>)); 
     typesToTest.Add(typeof(List<Dictionary<string, GenericTestClass<string>>>)); 
     typesToTest.Add(typeof(GenericTestClass<string>.SecondSubType<decimal>)); 
     typesToTest.Add(typeof(GenericTestClass<string>.SecondSubType)); 
     typesToTest.Add(typeof(GenericTestClass<string, int>.SecondSubType)); 
     typesToTest.Add(typeof(GenericTestClass<string, Dictionary<string,int>>.SecondSubType<string>)); 
     typesToTest.Add(typeof(GenericTestClass<string, Dictionary<string, int>>.SecondSubType<GenericTestClass<string, Dictionary<string, int>>>)); 


     foreach (var t in typesToTest) 
     { 
      if (GetCSCompilerName(t) != t.GetCSTypeName()) 
      { 
       Console.WriteLine($"FullName:\r\n{t.FullName}"); 
       Console.WriteLine("C " + GetCSCompilerName(t)); 
       Console.WriteLine("R " + t.GetCSTypeName()); 
       Console.WriteLine("Equal: " + (GetCSCompilerName(t) == t.GetCSTypeName())); 
       Console.WriteLine(); 

       Assert.Fail($"From CSharpCodeProvider '{GetCSCompilerName(t)}' is not equal to {t.GetCSTypeName()}"); 
      } 
      else 
      { 
       Console.WriteLine($"Passed: {t.GetCSTypeName()}"); 
       //ignore because of equal. 
      } 


     } 

    } 

    public class TestClass 
    { 

    } 

    public class GenericTestClass<T> 
    { 
     public class SecondSubType 
     { 

     } 

     public class SecondSubType<T2> 
     { 

     } 
    } 

    public class GenericTestClass<T1,T2> 
    { 
     public class SecondSubType 
     { 

     } 

     public class SecondSubType<T2> 
     { 

     } 
    } 
} 

resultado será:

Passed: string 
Passed: string[] 
Passed: object[] 
Passed: bool[] 
Passed: string 
Passed: object 
Passed: int 
Passed: double 
Passed: float 
Passed: bool 
Passed: char 
Passed: decimal 
Passed: System.Nullable<decimal>[] 
Passed: System.Nullable<decimal>[][] 
Passed: long 
Passed: System.Guid 
Passed: System.Nullable<int> 
Passed: System.Nullable<double> 
Passed: System.Nullable<float> 
Passed: System.Nullable<bool> 
Passed: System.Nullable<char> 
Passed: System.Nullable<decimal> 
Passed: System.Nullable<long> 
Passed: System.Nullable<System.Guid> 
Passed: System.Collections.Generic.List<string> 
Passed: System.Collections.Generic.Dictionary<string, System.Guid> 
Passed: System.Collections.Generic.Dictionary<string, System.Guid>[] 
Passed: System.Collections.Generic.Dictionary<string, System.Nullable<System.Guid>> 
Passed: System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, System.Nullable<System.Guid>>> 
Passed: System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, System.Nullable<System.Guid>>>[] 
Passed: System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, System.Nullable<System.Guid>>>[][] 
Passed: int[] 
Passed: int[][] 
Passed: int[][][] 
Passed: int[][][][] 
Passed: int[][][][][] 
Passed: Eneon.Common.Utils.Extensions.Tests.GetCSName.TestClass 
Passed: System.Collections.Generic.List<Eneon.Common.Utils.Extensions.Tests.GetCSName.TestClass> 
Passed: System.Collections.Generic.Dictionary<Eneon.Common.Utils.Extensions.Tests.GetCSName.TestClass, Eneon.Common.Utils.Extensions.Tests.GetCSName.TestClass> 
Passed: System.Collections.Generic.Dictionary<string, Eneon.Common.Utils.Extensions.Tests.GetCSName.TestClass> 
Passed: System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, Eneon.Common.Utils.Extensions.Tests.GetCSName.TestClass>> 
Passed: System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string>>> 
Passed: Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string>.SecondSubType<decimal> 
Passed: Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string>.SecondSubType 
Passed: Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string, int>.SecondSubType 
Passed: Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string, System.Collections.Generic.Dictionary<string, int>>.SecondSubType<string> 
Passed: Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string, System.Collections.Generic.Dictionary<string, int>>.SecondSubType<Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string, System.Collections.Generic.Dictionary<string, int>>> 
Cuestiones relacionadas