Unify TypeNameHelpers (dotnet/extensions#876)

\n\nCommit migrated from 2f53c36195
This commit is contained in:
Pavel Krymets 2018-12-31 12:54:54 -08:00 committed by GitHub
parent ebb5522f00
commit bdc2ea81c0
2 changed files with 80 additions and 20 deletions

View File

@ -9,6 +9,8 @@ namespace Microsoft.Extensions.Internal
{ {
internal class TypeNameHelper internal class TypeNameHelper
{ {
private const char DefaultNestedTypeDelimiter = '+';
private static readonly Dictionary<Type, string> _builtInTypeNames = new Dictionary<Type, string> private static readonly Dictionary<Type, string> _builtInTypeNames = new Dictionary<Type, string>
{ {
{ typeof(void), "void" }, { typeof(void), "void" },
@ -40,15 +42,17 @@ namespace Microsoft.Extensions.Internal
/// <param name="type">The <see cref="Type"/>.</param> /// <param name="type">The <see cref="Type"/>.</param>
/// <param name="fullName"><c>true</c> to print a fully qualified name.</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="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> /// <returns>The pretty printed type name.</returns>
public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false) public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false, bool includeGenericParameters = true, char nestedTypeDelimiter = DefaultNestedTypeDelimiter)
{ {
var builder = new StringBuilder(); var builder = new StringBuilder();
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames)); ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames, includeGenericParameters, nestedTypeDelimiter));
return builder.ToString(); return builder.ToString();
} }
private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options) private static void ProcessType(StringBuilder builder, Type type, in DisplayNameOptions options)
{ {
if (type.IsGenericType) if (type.IsGenericType)
{ {
@ -72,11 +76,17 @@ namespace Microsoft.Extensions.Internal
} }
else else
{ {
builder.Append(options.FullName ? type.FullName : type.Name); var name = options.FullName ? type.FullName : type.Name;
builder.Append(name);
if (options.NestedTypeDelimiter != DefaultNestedTypeDelimiter)
{
builder.Replace(DefaultNestedTypeDelimiter, options.NestedTypeDelimiter, builder.Length - name.Length, name.Length);
}
} }
} }
private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options) private static void ProcessArrayType(StringBuilder builder, Type type, in DisplayNameOptions options)
{ {
var innerType = type; var innerType = type;
while (innerType.IsArray) while (innerType.IsArray)
@ -95,7 +105,7 @@ namespace Microsoft.Extensions.Internal
} }
} }
private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, DisplayNameOptions options) private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, in DisplayNameOptions options)
{ {
var offset = 0; var offset = 0;
if (type.IsNested) if (type.IsNested)
@ -108,7 +118,7 @@ namespace Microsoft.Extensions.Internal
if (type.IsNested) if (type.IsNested)
{ {
ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options); ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options);
builder.Append('+'); builder.Append(options.NestedTypeDelimiter);
} }
else if (!string.IsNullOrEmpty(type.Namespace)) else if (!string.IsNullOrEmpty(type.Namespace))
{ {
@ -126,35 +136,44 @@ namespace Microsoft.Extensions.Internal
builder.Append(type.Name, 0, genericPartIndex); builder.Append(type.Name, 0, genericPartIndex);
builder.Append('<'); if (options.IncludeGenericParameters)
for (var i = offset; i < length; i++)
{ {
ProcessType(builder, genericArguments[i], options); builder.Append('<');
if (i + 1 == length) for (var i = offset; i < length; i++)
{ {
continue; ProcessType(builder, genericArguments[i], options);
} if (i + 1 == length)
{
continue;
}
builder.Append(','); builder.Append(',');
if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter)
{ {
builder.Append(' '); builder.Append(' ');
}
} }
builder.Append('>');
} }
builder.Append('>');
} }
private struct DisplayNameOptions private readonly struct DisplayNameOptions
{ {
public DisplayNameOptions(bool fullName, bool includeGenericParameterNames) public DisplayNameOptions(bool fullName, bool includeGenericParameterNames, bool includeGenericParameters, char nestedTypeDelimiter)
{ {
FullName = fullName; FullName = fullName;
IncludeGenericParameters = includeGenericParameters;
IncludeGenericParameterNames = includeGenericParameterNames; IncludeGenericParameterNames = includeGenericParameterNames;
NestedTypeDelimiter = nestedTypeDelimiter;
} }
public bool FullName { get; } public bool FullName { get; }
public bool IncludeGenericParameters { get; }
public bool IncludeGenericParameterNames { get; } public bool IncludeGenericParameterNames { get; }
public char NestedTypeDelimiter { get; }
} }
} }
} }

View File

@ -216,6 +216,47 @@ namespace Microsoft.Extensions.Internal
Assert.Equal(expected, actual); Assert.Equal(expected, actual);
} }
public static TheoryData<Type, string> FullTypeNameData
{
get
{
return new TheoryData<Type, string>
{
// Predefined Types
{ typeof(int), "int" },
{ typeof(List<int>), "System.Collections.Generic.List" },
{ typeof(Dictionary<int, string>), "System.Collections.Generic.Dictionary" },
{ typeof(Dictionary<int, List<string>>), "System.Collections.Generic.Dictionary" },
{ typeof(List<List<string>>), "System.Collections.Generic.List" },
// Classes inside NonGeneric class
{ typeof(A), "Microsoft.Extensions.Internal.TypeNameHelperTest.A" },
{ typeof(B<int>), "Microsoft.Extensions.Internal.TypeNameHelperTest.B" },
{ typeof(C<int, string>), "Microsoft.Extensions.Internal.TypeNameHelperTest.C" },
{ typeof(C<int, B<string>>), "Microsoft.Extensions.Internal.TypeNameHelperTest.C" },
{ typeof(B<B<string>>), "Microsoft.Extensions.Internal.TypeNameHelperTest.B" },
// Classes inside Generic class
{ typeof(Outer<int>.D), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.D" },
{ typeof(Outer<int>.E<int>), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.E" },
{ typeof(Outer<int>.F<int, string>), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.F" },
{ typeof(Outer<int>.F<int, Outer<int>.E<string>>),"Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.F" },
{ typeof(Outer<int>.E<Outer<int>.E<string>>), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.E" }
};
}
}
[Theory]
[MemberData(nameof(FullTypeNameData))]
public void Can_PrettyPrint_FullTypeName_WithoutGenericParametersAndNestedTypeDelimiter(Type type, string expectedTypeName)
{
// Arrange & Act
var displayName = TypeNameHelper.GetTypeDisplayName(type, fullName: true, includeGenericParameters: false, nestedTypeDelimiter: '.');
// Assert
Assert.Equal(expectedTypeName, displayName);
}
private class A { } private class A { }
private class B<T> { } private class B<T> { }