// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text; namespace Microsoft.Extensions.Internal { internal static class TypeNameHelper { private const char DefaultNestedTypeDelimiter = '+'; private static readonly Dictionary<Type, string> _builtInTypeNames = new Dictionary<Type, string> { { typeof(void), "void" }, { typeof(bool), "bool" }, { typeof(byte), "byte" }, { typeof(char), "char" }, { typeof(decimal), "decimal" }, { typeof(double), "double" }, { typeof(float), "float" }, { typeof(int), "int" }, { typeof(long), "long" }, { typeof(object), "object" }, { typeof(sbyte), "sbyte" }, { typeof(short), "short" }, { typeof(string), "string" }, { typeof(uint), "uint" }, { typeof(ulong), "ulong" }, { typeof(ushort), "ushort" } }; [return: NotNullIfNotNull(nameof(item))] public static string? GetTypeDisplayName(object? item, bool fullName = true) { return item == null ? null : GetTypeDisplayName(item.GetType(), fullName); } /// <summary> /// Pretty print a type name. /// </summary> /// <param name="type">The <see cref="Type"/>.</param> /// <param name="fullName"><c>true</c> to print a fully qualified name.</param> /// <param name="includeGenericParameterNames"><c>true</c> to include generic parameter names.</param> /// <param name="includeGenericParameters"><c>true</c> to include generic parameters.</param> /// <param name="nestedTypeDelimiter">Character to use as a delimiter in nested type names</param> /// <returns>The pretty printed type name.</returns> public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false, bool includeGenericParameters = true, char nestedTypeDelimiter = DefaultNestedTypeDelimiter) { StringBuilder? builder = null; string? name = ProcessType(ref builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames, includeGenericParameters, nestedTypeDelimiter)); return name ?? builder?.ToString() ?? string.Empty; } private static string? ProcessType(ref StringBuilder? builder, Type type, in DisplayNameOptions options) { if (type.IsGenericType) { Type[] genericArguments = type.GetGenericArguments(); builder ??= new StringBuilder(); ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options); } else if (type.IsArray) { builder ??= new StringBuilder(); ProcessArrayType(builder, type, options); } else if (_builtInTypeNames.TryGetValue(type, out string? builtInName)) { if (builder is null) return builtInName; builder.Append(builtInName); } else if (type.IsGenericParameter) { if (options.IncludeGenericParameterNames) { if (builder is null) return type.Name; builder.Append(type.Name); } } else { string name = options.FullName ? type.FullName! : type.Name; if (builder is null) { if (options.NestedTypeDelimiter != DefaultNestedTypeDelimiter) { return name.Replace(DefaultNestedTypeDelimiter, options.NestedTypeDelimiter); } return name; } builder.Append(name); if (options.NestedTypeDelimiter != DefaultNestedTypeDelimiter) { builder.Replace(DefaultNestedTypeDelimiter, options.NestedTypeDelimiter, builder.Length - name.Length, name.Length); } } return null; } private static void ProcessArrayType(StringBuilder builder, Type type, in DisplayNameOptions options) { Type innerType = type; while (innerType.IsArray) { innerType = innerType.GetElementType()!; } ProcessType(ref builder!, innerType, options); while (type.IsArray) { builder.Append('['); builder.Append(',', type.GetArrayRank() - 1); builder.Append(']'); type = type.GetElementType()!; } } private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, in DisplayNameOptions options) { int offset = 0; if (type.IsNested) { offset = type.DeclaringType!.GetGenericArguments().Length; } if (options.FullName) { if (type.IsNested) { ProcessGenericType(builder, type.DeclaringType!, genericArguments, offset, options); builder.Append(options.NestedTypeDelimiter); } else if (!string.IsNullOrEmpty(type.Namespace)) { builder.Append(type.Namespace); builder.Append('.'); } } int genericPartIndex = type.Name.IndexOf('`'); if (genericPartIndex <= 0) { builder.Append(type.Name); return; } builder.Append(type.Name, 0, genericPartIndex); if (options.IncludeGenericParameters) { builder.Append('<'); for (int i = offset; i < length; i++) { ProcessType(ref builder!, genericArguments[i], options); if (i + 1 == length) { continue; } builder.Append(','); if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) { builder.Append(' '); } } builder.Append('>'); } } private readonly struct DisplayNameOptions { public DisplayNameOptions(bool fullName, bool includeGenericParameterNames, bool includeGenericParameters, char nestedTypeDelimiter) { FullName = fullName; IncludeGenericParameters = includeGenericParameters; IncludeGenericParameterNames = includeGenericParameterNames; NestedTypeDelimiter = nestedTypeDelimiter; } public bool FullName { get; } public bool IncludeGenericParameters { get; } public bool IncludeGenericParameterNames { get; } public char NestedTypeDelimiter { get; } } } }