Подтвердить что ты не робот

С#: функция имени типа "Pretty"?

Свойства имени класса System.Type возвращают странный результат в случае общих типов. Есть ли способ получить имя типа в формате ближе к тому, как я его указал? Пример: typeof(List<string>).OriginalName == "List<string>"

4b9b3361

Ответ 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, если вам все равно.