From a198a599168f120337baf4140b669b763c10356f Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 28 May 2026 21:05:57 +0100 Subject: [PATCH] perf: dedupe GetSimpleTypeName into shared TypeNameFormatter Extract the duplicated runtime GetSimpleTypeName logic from TestDataFormatter and DisplayNameBuilder into a single internal TypeNameFormatter helper in TUnit.Core.Helpers. The shared implementation builds the entire recursive name with one pooled StringBuilder, allocating only the final string instead of a new string[] + string.Join per generic level. Output is byte-identical to both previous call sites. Closes #6039 --- TUnit.Core/DataSources/TestDataFormatter.cs | 28 +------- TUnit.Core/Helpers/TypeNameFormatter.cs | 71 +++++++++++++++++++++ TUnit.Engine/Helpers/DisplayNameBuilder.cs | 27 +------- 3 files changed, 73 insertions(+), 53 deletions(-) create mode 100644 TUnit.Core/Helpers/TypeNameFormatter.cs diff --git a/TUnit.Core/DataSources/TestDataFormatter.cs b/TUnit.Core/DataSources/TestDataFormatter.cs index b1cd16f0aa..1ef3428883 100644 --- a/TUnit.Core/DataSources/TestDataFormatter.cs +++ b/TUnit.Core/DataSources/TestDataFormatter.cs @@ -86,7 +86,7 @@ public static string CreateGenericDisplayName(TestMetadata metadata, Type[] gene var genericTypeNames = new string[genericTypes.Length]; for (var i = 0; i < genericTypes.Length; i++) { - genericTypeNames[i] = GetSimpleTypeName(genericTypes[i]); + genericTypeNames[i] = TypeNameFormatter.GetSimpleTypeName(genericTypes[i]); } var genericPart = string.Join(", ", genericTypeNames); testName = $"{testName}<{genericPart}>"; @@ -100,30 +100,4 @@ public static string CreateGenericDisplayName(TestMetadata metadata, Type[] gene var argumentsText = FormatArguments(arguments); return $"{testName}({argumentsText})"; } - - private static string GetSimpleTypeName(Type type) - { - if (!type.IsGenericType) - { - return type.Name; - } - - var genericTypeName = type.GetGenericTypeDefinition().Name; - var index = genericTypeName.IndexOf('`'); - if (index > 0) - { - genericTypeName = genericTypeName.Substring(0, index); - } - - var genericArgs = type.GetGenericArguments(); - var genericArgNames = new string[genericArgs.Length]; - for (var i = 0; i < genericArgs.Length; i++) - { - genericArgNames[i] = GetSimpleTypeName(genericArgs[i]); - } - var genericArgsText = string.Join(", ", genericArgNames); - - return $"{genericTypeName}<{genericArgsText}>"; - } - } diff --git a/TUnit.Core/Helpers/TypeNameFormatter.cs b/TUnit.Core/Helpers/TypeNameFormatter.cs new file mode 100644 index 0000000000..d14548b66a --- /dev/null +++ b/TUnit.Core/Helpers/TypeNameFormatter.cs @@ -0,0 +1,71 @@ +using System.Text; + +namespace TUnit.Core.Helpers; + +/// +/// Formats instances into simple, human-readable names for display purposes. +/// +internal static class TypeNameFormatter +{ + /// + /// Builds a simple type name, recursively expanding generic arguments + /// (e.g. Dictionary<String, List<Int32>>). + /// + /// + /// Uses a single pooled for the entire recursive build, + /// allocating only the final string rather than one string per generic level. + /// + public static string GetSimpleTypeName(Type type) + { + if (!type.IsGenericType) + { + return type.Name; + } + + var builder = StringBuilderPool.Get(); + try + { + AppendSimpleTypeName(builder, type); + return builder.ToString(); + } + finally + { + StringBuilderPool.Return(builder); + } + } + + private static void AppendSimpleTypeName(StringBuilder builder, Type type) + { + if (!type.IsGenericType) + { + builder.Append(type.Name); + return; + } + + var genericTypeName = type.GetGenericTypeDefinition().Name; + var index = genericTypeName.IndexOf('`'); + if (index > 0) + { + builder.Append(genericTypeName, 0, index); + } + else + { + builder.Append(genericTypeName); + } + + builder.Append('<'); + + var genericArgs = type.GetGenericArguments(); + for (var i = 0; i < genericArgs.Length; i++) + { + if (i > 0) + { + builder.Append(", "); + } + + AppendSimpleTypeName(builder, genericArgs[i]); + } + + builder.Append('>'); + } +} diff --git a/TUnit.Engine/Helpers/DisplayNameBuilder.cs b/TUnit.Engine/Helpers/DisplayNameBuilder.cs index 512236d892..d5e60c6696 100644 --- a/TUnit.Engine/Helpers/DisplayNameBuilder.cs +++ b/TUnit.Engine/Helpers/DisplayNameBuilder.cs @@ -110,7 +110,7 @@ public static string BuildGenericDisplayName(TestMetadata metadata, Type[] gener var genericTypeNames = new string[genericTypes.Length]; for (var i = 0; i < genericTypes.Length; i++) { - genericTypeNames[i] = GetSimpleTypeName(genericTypes[i]); + genericTypeNames[i] = TypeNameFormatter.GetSimpleTypeName(genericTypes[i]); } var genericPart = string.Join(", ", genericTypeNames); testName = $"{testName}<{genericPart}>"; @@ -125,31 +125,6 @@ public static string BuildGenericDisplayName(TestMetadata metadata, Type[] gener return $"{testName}({argumentsText})"; } - private static string GetSimpleTypeName(Type type) - { - if (!type.IsGenericType) - { - return type.Name; - } - - var genericTypeName = type.GetGenericTypeDefinition().Name; - var index = genericTypeName.IndexOf('`'); - if (index > 0) - { - genericTypeName = genericTypeName.Substring(0, index); - } - - var genericArgs = type.GetGenericArguments(); - var genericArgNames = new string[genericArgs.Length]; - for (var i = 0; i < genericArgs.Length; i++) - { - genericArgNames[i] = GetSimpleTypeName(genericArgs[i]); - } - var genericArgsText = string.Join(", ", genericArgNames); - - return $"{genericTypeName}<{genericArgsText}>"; - } - /// /// Resolves the actual value from a data source factory result ///