Свойства имени класса System.Type возвращают странный результат в случае общих типов. Есть ли способ получить имя типа в формате ближе к тому, как я его указал?
Пример: typeof(List<string>).OriginalName == "List<string>"
С#: функция имени типа "Pretty"?
Ответ 1
Я понял, что должен сам написать это. Вот мое решение (на самом деле это немного больше, чем просили). Это, вероятно, полезно.
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
Проблема с "хорошими" именами заключается в том, что они различаются в зависимости от языка, который вы используете. Представьте себе удивление разработчика VB.NET, если OriginalName
вернулся синтаксис С#.
Однако, довольно легко сделать это самостоятельно:
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>>
, оно все равно будет работать.
Ответ 3
Я использовал 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);
}
Ответ 4
Вы должны написать это сами. Имейте в виду, что Type.Name
и т.д. Вызывают методы, которые живут в среде CLR и могут быть вызваны с нескольких языков. Вот почему они не возвращаются, как С# или VB, или язык, на который был закодирован вызывающий, но вместо этого выглядит как представление CLR.
Обратите внимание, что string
и что не являются псевдонимами для типов CLR типа System.String
. Опять же, это играет определенную роль в форматировании, которое вы видите.
Это не сложно сделать, используя отражение, но я сомневаюсь в его значении.
Ответ 5
Как и Гарольд Хойер, но включающий 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;
}
}
Ответ 6
Вот моя реализация. Он был создан для описания методов, поэтому он обрабатывает ключевые слова ref
и 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;
}
Вызывающий код выглядит следующим образом:
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()));
Где method
- экземпляр MethodInfo
.
Одна нота: мне не нужно было описывать какие-либо типы многомерных массивов, поэтому я не стал описывать это описание, но было бы довольно легко добавить, вызвав type.GetArrayRank()
.
Ответ 7
Минимальное рабочее решение, использующее 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;
}
Используя этот модифицированный метод factory, тогда можно использовать соответствующий поставщик кода для получения типичного ввода, например:
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
, чем что-либо еще.
Ответ 8
как в вашем примере, вы можете ожидать тип, чтобы вы могли попробовать это
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
}
Ответ 9
Я понимаю, что вы хотите сравнить типы.
Лучший способ сделать это -...
myVar is List<string>
или myVar.GetType() == myOtherVar.GetType()
Если вам это не нужно... пожалуйста, игнорируйте мой ответ.
Ответ 10
Во-первых: Престижность Navid для предотвращения повторного использования колес. Я бы поднял, если бы мог.
Вот несколько моментов для добавления, если кто-то пойдет по этому пути (по крайней мере, для VS10/.Net 4):
* Попробуйте использовать один из вариантов CodeTypeReference с аргументами типа. Это позволяет избежать ряда ошибок использования имен типа строк (например, trailing &) и означает, что вы возвращаетесь bool
вместо System.Boolean
и т.д. Вы возвращаете полное пространство имен для многих типов, подобных этому, но можете всегда избавляйтесь от части пространства имен позже.
* Простые Nullables имеют тенденцию возвращаться в форме System.Nullable<int>
, а не int?
. Вы можете преобразовать в более удобный синтаксис с регулярным выражением в ответ, например:
const string NullablePattern = @"System.Nullable<(?<nulledType>[\w\.]+)>";
const string NullableReplacement = @"${nulledType}?";
answer = Regex.Replace(answer, NullablePattern, NullableReplacement);
* Тип аргумента метода, такого как out T?
, дает очень непрозрачную строку. Если у кого-то есть элегантный способ справиться с такими вещами, я хотел бы узнать об этом.
Надеюсь, с Рослин все станет очень легко.
Ответ 11
Я делаю это так
public static class TypeExtensions {
public static String GetName(this Type t) {
String readable;
#if PREVENT_RECURSION
if(m_names.TryGetValue(t, out readable)) {
return readable;
}
#endif
var tArgs = t.IsGenericType ? t.GetGenericArguments() : Type.EmptyTypes;
var name = t.Name;
var format = Regex.Replace(name, @"'\d+.*", "")+(t.IsGenericType ? "<?>" : "");
var names = tArgs.Select(x => x.IsGenericParameter ? "" : GetName(x));
readable=String.Join(String.Join(",", names), format.Split('?'));
#if PREVENT_RECURSION
m_names.Add(t, readable);
#endif
return readable;
}
static readonly Dictionary<Type, String> m_names = new Dictionary<Type, String> { };
}
где PREVENT_RECURSION
должно быть определено как true
если вы не хотите, чтобы тип в аргументах универсального типа каждый раз разрешался рекурсивно, просто возьмите его из словаря кэша; оставьте неопределенным значение false, если вам все равно.