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
///