2010-03-15 7 views

Répondre

53

Je vois que vous avez déjà accepté une réponse, mais honnêtement, cette réponse ne suffira pas à le faire de manière fiable si vous combinez simplement ce qui est là avec ce que vous avez déjà écrit. C'est sur la bonne voie, mais votre code ne fonctionnera que pour les types génériques avec exactement un paramètre générique, et cela ne fonctionnera que lorsque le paramètre de type générique lui-même n'est pas générique!

Cette fonction (écrite comme une méthode d'extension) qui devrait en fait travailler dans tous les cas:

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 + ">"; 
    } 
} 

Cette fonction est récursive et sûr. Si vous exécutez sur cette entrée:

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

vous obtenez ce (correct) Sortie:

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

Il est dommage que le CLR ne vient pas avec cette fonction. –

+3

@Paul Ruane: Je suis d'accord qu'il pourrait être utile dans certains cas, cependant, le CLR est indépendant de la langue, et il n'y aurait aucun moyen d'implémenter quelque chose comme ça pour travailler aussi bien en C#, VB.NET, F #, IronPython et tous les autres langages CLR. Le nom bizarre avec le backtick est en fait le vrai nom de type CLR; le format ci-dessus est spécifique à C#. – Aaronaught

+0

Ah oui, bon point. J'ai la vision du tunnel C#! –

0

ajout mineur à @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

On dirait que la seule chose que vous avez changé est de 'Name' à' Nom complet? Je ne suis pas sûr que cela change suffisamment pour justifier une réponse alternative - il aurait probablement dû être publié en tant que commentaire. – Aaronaught

+0

Ce n'est pas une alternative. Fullname aide avec la résolution de l'espace de noms. –

5

Alors que la solution retenue est bon pour seulement le nom ou pour un nom complet non imbriqué (en remplaçant le nom par le nom complet comme dans la réponse de @Ose E), cependant pour les types imbriqués, il ne fonctionnera toujours pas, et pas pour les tableaux de types génériques. Voici donc une solution qui fonctionnera (mais notez que cette solution ne fera que définir les arguments réels, seulement si tous les arguments sont définis, et en d'autres termes même si le type déclarant a fourni des arguments de type, aussi longtemps que le type générique le plus interne n'a pas, il n'apparaîtra toujours pas même pour 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

Utilisé cette solution sur un projet sur lequel je travaille, fonctionne très bien – Oren

0

C'est ma solution, il travaille aussi pour les classes imbriquées et génériques:

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

Cela se traduira par exactement le même résultat de code comme le générateur de code cs. J'ai amélioré le code 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; 
    } 

cela passera le test unitaire suivant comme prévu.

[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> 
     { 

     } 
    } 
} 

Le résultat sera:

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>>> 
Questions connexes