2011-06-19 11 views
17

Właściwości nazwy klasy System.Type zwracają dziwny wynik w przypadku typów ogólnych. Czy istnieje sposób na uzyskanie nazwy typu w bliższym formacie, jaki określiłem? Przykład: typeof(List<string>).OriginalName == "List<string>"C#: Funkcja nazwy typu "Pretty"?

+0

To dość łatwo napisać siebie jako metodę rozszerzenia przy użyciu rekursji. –

+2

Taka funkcja nie jest w ramach, ponieważ zależy to od używanego języka: VB ma inną reprezentację generycznych i jestem pewien, że również inne języki. – svick

Odpowiedz

0

Zrozumiałem, że muszę sam to napisać. Oto moje rozwiązanie (w rzeczywistości jest to trochę więcej niż wymagano). Jest to prawdopodobnie pomocne.

using System.Reflection; 
using HWClassLibrary.Debug; 
using System.Collections.Generic; 
using System.Linq; 
using System; 

namespace HWClassLibrary.Helper 
{ 
    public static class TypeNameExtender 
    { 
     private static IEnumerable<Type> _referencedTypesCache; 

     public static void OnModuleLoaded() { _referencedTypesCache = null; } 

     public static string PrettyName(this Type type) 
     { 
      if(type == typeof(int)) 
       return "int"; 
      if(type == typeof(string)) 
       return "string"; 

      var result = PrettyTypeName(type); 
      if(type.IsGenericType) 
       result = result + PrettyNameForGeneric(type.GetGenericArguments()); 
      return result; 
     } 

     private static string PrettyTypeName(Type type) 
     { 
      var result = type.Name; 
      if(type.IsGenericType) 
       result = result.Remove(result.IndexOf('`')); 

      if (type.IsNested && !type.IsGenericParameter) 
       return type.DeclaringType.PrettyName() + "." + result; 

      if(type.Namespace == null) 
       return result; 

      var conflictingTypes = ReferencedTypes 
       .Where(definedType => definedType.Name == type.Name && definedType.Namespace != type.Namespace) 
       .ToArray(); 

      var namespaceParts = type.Namespace.Split('.').Reverse().ToArray(); 
      var namespacePart = ""; 
      for(var i = 0; i < namespaceParts.Length && conflictingTypes.Length > 0; i++) 
      { 
       namespacePart = namespaceParts[i] + "." + namespacePart; 
       conflictingTypes = conflictingTypes 
        .Where(conflictingType => (conflictingType.Namespace + ".").EndsWith(namespacePart)) 
        .ToArray(); 
      } 

      return namespacePart + result; 
     } 

     private static IEnumerable<Type> ReferencedTypes 
     { 
      get 
      { 
       if(_referencedTypesCache == null) 
        _referencedTypesCache = Assembly.GetEntryAssembly().GetReferencedTypes(); 
       return _referencedTypesCache; 
      } 
     } 

     private static string PrettyNameForGeneric(Type[] types) 
     { 
      var result = ""; 
      var delim = "<"; 
      foreach(var t in types) 
      { 
       result += delim; 
       delim = ","; 
       result += t.PrettyName(); 
      } 
      return result + ">"; 
     } 
    } 
} 
2

Musisz napisać to sam. Należy pamiętać, że Type.Name itd. Wywołują metody, które znajdują się w CLR i mogą być wywoływane z wielu języków. To dlatego nie wracają, wyglądając jak C# lub VB, lub język, w którym był kodowany wywołujący, ale zamiast tego wygląda jak reprezentacja CLR.

Należy pamiętać, że string i co nie są aliasy dla typów CLR, takich jak System.String. Ponownie, odgrywa to rolę w formatowaniu, które widzisz.

Nie jest trudno zrobić, używając refleksji, ale kwestionuję jej wartość.

+0

Chciałbym wiedzieć, dlaczego to zostało odrzucone. – jason

0

jak w przykładzie można oczekiwać typ więc można spróbować

public class test<T> where T : class 
{ 
    public List<String> tt 
    { 
     get; 
     set; 
    } 
} 
/////////////////////////// 
test<List<String>> tt = new test<List<String>>(); 
if(tt.GetType().FullName.Contains(TypeOf(List<String>).FullName)) 
{ 
    //do something 
} 
else 
{ 
    //do something else 
} 
28

Problem z „czystych” imion to są one różne w zależności od języka, którego używasz. Wyobraźmy sobie niespodziankę programisty VB.NET, jeśli OriginalName zwróci składnię C#.

Jednak jest to dość dość łatwo zrobić to samemu:

private static string PrettyName(Type type) 
{ 
    if (type.GetGenericArguments().Length == 0) 
    { 
     return type.Name; 
    } 
    var genericArguments = type.GetGenericArguments(); 
    var typeDefeninition = type.Name; 
    var unmangledName = typeDefeninition.Substring(0, typeDefeninition.IndexOf("`")); 
    return unmangledName + "<" + String.Join(",", genericArguments.Select(PrettyName)) + ">"; 
} 

To będzie rekursywnie rozwiązać nazwę niezarządzanej, tak, że jeśli masz coś jak Dictionary<string, IList<string>> powinna nadal działać.

+4

Idealnie wymaga to dalszego rozwoju do obsługi typów takich jak 'Dictionary > .KeyCollection'. Jego nazwa typu CLR to 'Dictionary \' 2 + KeyCollection [System.String, IList \ '1 [System.String]]' (dawaj lub weź kwalifikatory obszarów nazw). Ponadto możesz mieć takie rzeczy jak 'Outer \' 1 + Inner \ '1 [OuterArg, InnerArg]'. Oczywiście macie tablice (1D 2D itd.) I wskaźniki do typów ogólnych, które wymagają obsługi. :) – entheh

14

użyłem CodeDomProvider przekonwertować do C#:

public static string GetOriginalName(this Type type) 
    { 
     string TypeName = type.FullName.Replace(type.Namespace + ".", "");//Removing the namespace 

     var provider = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("CSharp"); //You can also use "VisualBasic" 
     var reference = new System.CodeDom.CodeTypeReference(TypeName); 

     return provider.GetTypeOutput(reference); 
    } 
+0

Co jeśli chciałbym pominąć przestrzeń nazw? – svick

+0

Można użyć type.Name zamiast type.FullName Myślę, że nie musisz samodzielnie usuwać obszaru nazw. Przepraszamy, teraz widzę, że nie otrzymujesz ogólnych parametrów w ten sposób. – riezebosch

0

Rozumiem, że chcesz porównać typy.
Najlepszym sposobem na to jest ...
myVar is List<string> lub
myVar.GetType() == myOtherVar.GetType()

Jeśli nie potrzebujesz tego ... proszę zignorować moją odpowiedź.

2

jak odpowiedź Harolda Hoyer, ale w tym nullables i jeszcze kilka wbudowanych typów:

/// <summary> 
/// Get full type name with full namespace names 
/// </summary> 
/// <param name="type"> 
/// The type to get the C# name for (may be a generic type or a nullable type). 
/// </param> 
/// <returns> 
/// Full type name, fully qualified namespaces 
/// </returns> 
public static string CSharpName(this Type type) 
{ 
    Type nullableType = Nullable.GetUnderlyingType(type); 
    string nullableText; 
    if (nullableType != null) 
    { 
     type = nullableType; 
     nullableText = "?"; 
    } 
    else 
    { 
     nullableText = string.Empty; 
    } 

    if (type.IsGenericType) 
    { 
     return string.Format(
      "{0}<{1}>{2}", 
      type.Name.Substring(0, type.Name.IndexOf('`')), 
      string.Join(", ", type.GetGenericArguments().Select(ga => ga.CSharpName())), 
      nullableText); 
    } 

    switch (type.Name) 
    { 
     case "String": 
      return "string"; 
     case "Int32": 
      return "int" + nullableText; 
     case "Decimal": 
      return "decimal" + nullableText; 
     case "Object": 
      return "object" + nullableText; 
     case "Void": 
      return "void" + nullableText; 
     default: 
      return (string.IsNullOrWhiteSpace(type.FullName) ? type.Name : type.FullName) + nullableText; 
    } 
} 
0

pierwszy: Uznanie dla Navid dla unikania reinvention koła. Chciałbym przegłosować, gdybym mógł.

Oto kilka punktów do dodania, jeśli ktokolwiek podąża tą ścieżką (przynajmniej dla VS10/.Net 4):
* Spróbuj użyć jednego z wariantów CodeTypeReference z argumentami typu. Pozwala to uniknąć wielu pułapek używania nazw typów ciągów (takich jak końcowe: &) i oznacza, że ​​odzyskasz bool zamiast System.Boolean itd. Otrzymujesz pełny obszar nazw dla wielu typów takich jak ten, ale zawsze możesz się pozbyć część przestrzeni nazw później.
* Simple Nullables wydają się wracać w postaci System.Nullable<int> zamiast int? - Można konwertować do ładniejszy składni z regex na odpowiedź, takich jak: const string NullablePattern = @"System.Nullable<(?<nulledType>[\w\.]+)>"; const string NullableReplacement = @"${nulledType}?"; answer = Regex.Replace(answer, NullablePattern, NullableReplacement);
* rodzaj metody argument jak out T? daje bardzo nieprzejrzysty ciąg . Jeśli ktoś ma elegancki sposób radzenia sobie z takimi rzeczami, chciałbym o tym wiedzieć.

Mam nadzieję, że wszystko to stanie się bardzo łatwe z Roslyn.

+0

Przepraszam, wzór Nullable został zaszyfrowany. Powinien być @"System\.Nullable<(?[\w\.]+)>";

2

Oto moja implementacja. Został stworzony do opisywania metod, więc obsługuje słowa kluczowe: ref i out.

private static Dictionary<Type, string> shorthandMap = new Dictionary<Type, string> 
{ 
    { typeof(Boolean), "bool" }, 
    { typeof(Byte), "byte" }, 
    { typeof(Char), "char" }, 
    { typeof(Decimal), "decimal" }, 
    { typeof(Double), "double" }, 
    { typeof(Single), "float" }, 
    { typeof(Int32), "int" }, 
    { typeof(Int64), "long" }, 
    { typeof(SByte), "sbyte" }, 
    { typeof(Int16), "short" }, 
    { typeof(String), "string" }, 
    { typeof(UInt32), "uint" }, 
    { typeof(UInt64), "ulong" }, 
    { typeof(UInt16), "ushort" }, 
}; 

private static string CSharpTypeName(Type type, bool isOut = false) 
{ 
    if (type.IsByRef) 
    { 
     return String.Format("{0} {1}", isOut ? "out" : "ref", CSharpTypeName(type.GetElementType())); 
    } 
    if (type.IsGenericType) 
    { 
     if (type.GetGenericTypeDefinition() == typeof(Nullable<>)) 
     { 
      return String.Format("{0}?", CSharpTypeName(Nullable.GetUnderlyingType(type))); 
     } 
     else 
     { 
      return String.Format("{0}<{1}>", type.Name.Split('`')[0], 
       String.Join(", ", type.GenericTypeArguments.Select(a => CSharpTypeName(a)).ToArray())); 
     } 
    } 
    if (type.IsArray) 
    { 
     return String.Format("{0}[]", CSharpTypeName(type.GetElementType())); 
    } 

    return shorthandMap.ContainsKey(type) ? shorthandMap[type] : type.Name; 
} 

kod Powołanie wygląda następująco:

string line = String.Format("{0}.{1}({2})", 
    method.DeclaringType.Name, 
    method.Name, 
    String.Join(", ", method.GetParameters().Select(p => CSharpTypeName(p.ParameterType, p.IsOut) + " " + p.Name).ToArray())); 

Gdzie method jest wystąpienie MethodInfo.

Jedna uwaga: nie musiałem opisywać żadnych typów tablic wielowymiarowych, więc nie zawracałem sobie głowy zaimplementowaniem tego opisu, ale byłoby całkiem łatwo dodać, dzwoniąc pod numer type.GetArrayRank().

0

Minimalne rozwiązanie do pracy, które wykorzystuje CodeDomProvider, to kontrola sposobu budowania instancji CodeTypeReference w pierwszej kolejności. Istnieją specjalne przypadki tylko dla typów generycznych i tablic multi-Rank, więc mamy tylko dbać o tych:

static CodeTypeReference CreateTypeReference(Type type) 
{ 
    var typeName = (type.IsPrimitive || type == typeof(string)) ? type.FullName : type.Name; 
    var reference = new CodeTypeReference(typeName); 
    if (type.IsArray) 
    { 
     reference.ArrayElementType = CreateTypeReference(type.GetElementType()); 
     reference.ArrayRank = type.GetArrayRank(); 
    } 

    if (type.IsGenericType) 
    { 
     foreach (var argument in type.GetGenericArguments()) 
     { 
      reference.TypeArguments.Add(CreateTypeReference(argument)); 
     } 
    } 
    return reference; 
} 

Użycie tej zmodyfikowanej metody fabryki, to wtedy można użyć odpowiedniego dostawcy kodu, aby uzyskać ładny pisania , tak jak:

using (var provider = new CSharpCodeProvider()) 
{ 
    var reference = CreateTypeReference(typeof(IObservable<IEnumerable<Tuple<int?, string>>>[,])); 
    var output = provider.GetTypeOutput(reference); 
    Console.WriteLine(output); 
} 

Powyższy kod zwraca IObservable<IEnumerable<Tuple<Nullable<int>, string>>>[,]. Jedyny szczególny przypadek, który nie jest dobrze obsługiwany, to typy Nullable, ale jest to bardziej błędem niż w jakimkolwiek innym przypadku.