2010-03-15 1 views

risposta

53

Vedo che hai già accettato una risposta, ma onestamente, questa risposta non sarà sufficiente per farlo in modo affidabile se combinerai semplicemente ciò che è lì dentro con ciò che hai già scritto. È sulla strada giusta, ma il tuo codice funzionerà solo con tipi generici con esattamente un parametro generico, e funzionerà solo quando il parametro generico di tipo non è generico!

Questa è una funzione (scritto come un metodo di estensione) che dovrebbe effettivamente lavoro in tutti i casi:

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

Questa funzione è ricorsiva e sicuro. Se lo si esegue su questo ingresso:

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

si ottiene questo (corretto) Uscita:

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

E 'un peccato il CLR non sono dotati di questa funzione. –

+3

@Paul Ruane: Sono d'accordo che potrebbe essere utile in certi casi, tuttavia, il CLR è indipendente dalla lingua e non ci sarebbe alcun modo di implementare qualcosa di simile in modo da funzionare altrettanto bene in C#, VB.NET, F #, IronPython e tutte le altre lingue CLR. Il nome dall'aspetto strano con l'apice è in realtà il vero nome del tipo CLR; il formato sopra è C# specifico. – Aaronaught

+0

Ah si, buon punto. Ho una visione del tunnel C#! –

0

Oltre Minore @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

Sembra che l'unica cosa che hai cambiato sia da 'Nome' a' FullName'? Non sono sicuro che ciò cambi materialmente abbastanza da giustificare una risposta alternativa - probabilmente sarebbe stato pubblicato come commento. – Aaronaught

+0

Non è un'alternativa. Fullname aiuta con la risoluzione dello spazio dei nomi. –

5

Mentre la soluzione accettata è buono solo per il nome o un nome completo non nidificato (sostituendo il nome al nome completo come nella risposta di @Ose E), tuttavia per i tipi annidati non funzionerà ancora, e anche per gli array di tipi generici.

Quindi ecco una soluzione che funzionerà, (ma si noti che questa soluzione imposterà solo gli argomenti effettivi, solo se tutti gli argomenti sono impostati, e in altre parole anche se il tipo di dichiarazione ha fornito argomenti di tipo, purché il tipo più generico più interno non lo è, non verrà ancora visualizzato anche per 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

Ho usato questa soluzione per un progetto su cui sto lavorando, funziona alla grande – Oren

0

Questa è la mia soluzione, è anche lavorando per le classi annidate e generici:

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

Ciò comporterà esattamente lo stesso risultato codice come il generatore di codice cs. Ho migliorato il codice di 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; 
    } 

questo supererà il test dell'unità seguente come previsto.

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

     } 
    } 
} 

Risultato sarà:

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