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
{
private const char DefaultNestedTypeDelimiter = '+';
private static readonly Dictionary<Type, string> _builtInTypeNames = new Dictionary<Type, string>
{
{ typeof(void), "void" },
@ -40,15 +42,17 @@ namespace Microsoft.Extensions.Internal
/// <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)
public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false, bool includeGenericParameters = true, char nestedTypeDelimiter = DefaultNestedTypeDelimiter)
{
var builder = new StringBuilder();
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames, includeGenericParameters, nestedTypeDelimiter));
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)
{
@ -72,11 +76,17 @@ namespace Microsoft.Extensions.Internal
}
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;
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;
if (type.IsNested)
@ -108,7 +118,7 @@ namespace Microsoft.Extensions.Internal
if (type.IsNested)
{
ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options);
builder.Append('+');
builder.Append(options.NestedTypeDelimiter);
}
else if (!string.IsNullOrEmpty(type.Namespace))
{
@ -126,35 +136,44 @@ namespace Microsoft.Extensions.Internal
builder.Append(type.Name, 0, genericPartIndex);
builder.Append('<');
for (var i = offset; i < length; i++)
if (options.IncludeGenericParameters)
{
ProcessType(builder, genericArguments[i], options);
if (i + 1 == length)
builder.Append('<');
for (var i = offset; i < length; i++)
{
continue;
}
ProcessType(builder, genericArguments[i], options);
if (i + 1 == length)
{
continue;
}
builder.Append(',');
if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter)
{
builder.Append(' ');
builder.Append(',');
if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter)
{
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;
IncludeGenericParameters = includeGenericParameters;
IncludeGenericParameterNames = includeGenericParameterNames;
NestedTypeDelimiter = nestedTypeDelimiter;
}
public bool FullName { get; }
public bool IncludeGenericParameters { get; }
public bool IncludeGenericParameterNames { get; }
public char NestedTypeDelimiter { get; }
}
}
}

View File

@ -216,6 +216,47 @@ namespace Microsoft.Extensions.Internal
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 B<T> { }