2011-06-19 8 views
17

System.Typeクラスのnameプロパティは、ジェネリック型の場合には奇妙な結果を返します。指定した方法に近い形式で型名を取得する方法はありますか? 例:typeof(List<string>).OriginalName == "List<string>"C#: "Pretty"型名関数?

+0

これは、再帰を使用して拡張メソッドとして自分自身を書くのはかなり簡単です。 –

+2

このような関数はフレームワークにはありません。なぜなら、使用する言語に依存するからです.VBはジェネリックの表現が異なり、他の言語もそうです。 – svick

答えて

0

私は自分自身でこれを書かなければならないことを理解しました。ここに私の解決策があります(それは実際には尋ねるよりも少しだけです)。それは、おそらく、有用です。

ハロルド・ホイヤーの答えのように
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

これはあなた自身で書く必要があります。 Type.NameなどがCLRに存在するメソッドを呼び出すことに留意し、複数の言語から呼び出すことができます。これは、C#やVBやコーラーがコーディングされた言語のように見えるのではなく、代わりにCLR表現のように見える理由です。

さらに、stringと、System.StringのようなCLRタイプのエイリアスはありません。ここでも、これは表示される書式設定の役割を果たします。

リフレクションを使用するのは難しくありませんが、私はそれの価値について質問します。

+0

私はこれがなぜ落とされたのか知りたいです。 – jason

0

あなたはタイプを期待することができ、あなたの例のように、あなたが

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

ことを試すことができますので、「かわいい」の名前の問題は、彼らが使用している言語によって異なりますです。 OriginalNameがC#構文を返した場合、VB.NET開発者の驚きを想像してみてください。

しかし、それはこの自分で作ることはかなりかなり簡単です:

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

あなたがDictionary<string, IList<string>>のようなものを持っている場合、それはまだ動作しなければならないように、これは再帰的に、管理されていない名前を解決します。

+4

理想的には、 'Dictionary > .KeyCollection'のような型を処理するための開発が必要です。そのCLR型名は 'Dictionary \' 2 + KeyCollection [System.String、IList \ '1 [System.String]]'です(名前空間修飾子を与えたり取ります)。さらに、あなたは 'Outer \' 1 + Inner \ '1 [OuterArg、InnerArg]'のようなものを持つことができます。もちろん、配列(1D 2Dなど)と汎用型へのポインタを持つことができます。ハンドリングが必要です。 :) – entheh

14

私はC#に変換しCodeDomProviderを使用:

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

名前空間を残したい場合はどうすればいいですか? – svick

+0

type.FullNameの代わりにtype.Nameを使うことができますので、自分で名前空間を削除する必要はありません。申し訳ありませんが、私はあなたがそのような一般的なパラメータを取得していないことがわかりました。 – riezebosch

0

私はあなたがタイプを比較したいことを理解しています。あなたがこれを必要としない場合は、これを行うために
最良の方法は...
myVar is List<string>または
myVar.GetType() == myOtherVar.GetType()

です...私の答えを無視してください。

2

が、nullablesとビルトインタイプのいくつかのより多くを含む:

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

まず:ホイール改革回避のためと、Navidに拍手を送りたいです。私はできればアップヴォートするだろう。
*型引数を持つCodeTypeReferenceの変種のいずれかを使用してみてください:誰もがこのパス(少なくともVS10のために/ネット4)ダウンした場合ここで

は、追加するためのいくつかのポイントです。これにより、文字列型名(例えば、末尾に&など)を使用することの落とし穴が回避され、System.Booleanなどの代わりにboolが返されることを意味します。このような多くの種類の完全な名前空間は返されますが、いつでも後で名前空間部分。
*シンプルNullablesはフォームSystem.Nullable<int>はなくint?に戻ってくる傾向がある - あなたは答えに正規表現でよりよい構文に変換することができますような: const string NullablePattern = @"System.Nullable<(?<nulledType>[\w\.]+)>"; const string NullableReplacement = @"${nulledType}?"; answer = Regex.Replace(answer, NullablePattern, NullableReplacement);
* out T?ようなメソッドの引数の型が非常に不透明な文字列を与えます。このようなことに誰かが優雅なやり方をしているなら、私はそれについて知りたいです。

これはすべてRoslynで非常に簡単になることを願っています。

+0

申し訳ありませんが、ヌルパターンがスクランブルされました。 @"System\.Nullable<(?[\w\.]+)>";である必要があります。 –

2

これは私の実装です。メソッドを記述するために作成されたため、refoutのキーワードを処理します。 methodMethodInfoインスタンスである

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())); 

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

呼び出し元のコードは次のようになります。

1つの注記:多次元配列型を記述する必要はありませんでした。そのために説明を実装するのはやめましたが、type.GetArrayRank()を呼び出すとかなり簡単に追加できます。

0

CodeDomProviderを活用する最小限の作業ソリューションは、CodeTypeReferenceインスタンスが最初に構築される方法を制御することです。そこだけジェネリック型とマルチランクアレイ用の特別な場合であるので、我々は唯一のものを気にする必要があります。

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

この修正されたファクトリメソッドを使用して、かなりのタイピングを取得するために、適切なコードプロバイダを使用することが可能です次のようになります。

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

上記のコードは、IObservable<IEnumerable<Tuple<Nullable<int>, string>>>[,]を返します。特別に処理されない特殊なケースはNullableタイプですが、これは他のタイプよりも実際にはCodeDomProviderの欠陥です。