Skip to content

Commit ee0587e

Browse files
authored
Merge pull request #622 from CommunityToolkit/dev/remove-diagnostics-linq
Drop System.Linq from CommunityToolkit.Diagnostics
2 parents 93a57d3 + af2cbf3 commit ee0587e

File tree

1 file changed

+115
-88
lines changed

1 file changed

+115
-88
lines changed

src/CommunityToolkit.Diagnostics/Extensions/TypeExtensions.cs

Lines changed: 115 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
using System;
66
using System.Collections.Generic;
7-
using System.Linq;
87
using System.Runtime.CompilerServices;
98

109
namespace CommunityToolkit.Diagnostics;
@@ -43,97 +42,12 @@ public static class TypeExtensions
4342
private static readonly ConditionalWeakTable<Type, string> DisplayNames = new();
4443

4544
/// <summary>
46-
/// Returns a simple string representation of a type.
45+
/// Returns a simple <see cref="string"/> representation of a type.
4746
/// </summary>
4847
/// <param name="type">The input type.</param>
49-
/// <returns>The string representation of <paramref name="type"/>.</returns>
48+
/// <returns>The <see cref="string"/> representation of <paramref name="type"/>.</returns>
5049
public static string ToTypeString(this Type type)
5150
{
52-
// Local function to create the formatted string for a given type
53-
static string FormatDisplayString(Type type, int genericTypeOffset, ReadOnlySpan<Type> typeArguments)
54-
{
55-
// Primitive types use the keyword name
56-
if (BuiltInTypesMap.TryGetValue(type, out string? typeName))
57-
{
58-
return typeName!;
59-
}
60-
61-
// Array types are displayed as Foo[]
62-
if (type.IsArray)
63-
{
64-
Type? elementType = type.GetElementType()!;
65-
int rank = type.GetArrayRank();
66-
67-
return $"{FormatDisplayString(elementType, 0, elementType.GetGenericArguments())}[{new string(',', rank - 1)}]";
68-
}
69-
70-
// By checking generic types here we are only interested in specific cases,
71-
// ie. nullable value types or value tuples. We have a separate path for custom
72-
// generic types, as we can't rely on this API in that case, as it doesn't show
73-
// a difference between nested types that are themselves generic, or nested simple
74-
// types from a generic declaring type. To deal with that, we need to manually track
75-
// the offset within the array of generic arguments for the whole constructed type.
76-
if (type.IsGenericType)
77-
{
78-
Type? genericTypeDefinition = type.GetGenericTypeDefinition();
79-
80-
// Nullable<T> types are displayed as T?
81-
if (genericTypeDefinition == typeof(Nullable<>))
82-
{
83-
Type[]? nullableArguments = type.GetGenericArguments();
84-
85-
return $"{FormatDisplayString(nullableArguments[0], 0, nullableArguments)}?";
86-
}
87-
88-
// ValueTuple<T1, T2> types are displayed as (T1, T2)
89-
if (genericTypeDefinition == typeof(ValueTuple<>) ||
90-
genericTypeDefinition == typeof(ValueTuple<,>) ||
91-
genericTypeDefinition == typeof(ValueTuple<,,>) ||
92-
genericTypeDefinition == typeof(ValueTuple<,,,>) ||
93-
genericTypeDefinition == typeof(ValueTuple<,,,,>) ||
94-
genericTypeDefinition == typeof(ValueTuple<,,,,,>) ||
95-
genericTypeDefinition == typeof(ValueTuple<,,,,,,>) ||
96-
genericTypeDefinition == typeof(ValueTuple<,,,,,,,>))
97-
{
98-
IEnumerable<string>? formattedTypes = type.GetGenericArguments().Select(t => FormatDisplayString(t, 0, t.GetGenericArguments()));
99-
100-
return $"({string.Join(", ", formattedTypes)})";
101-
}
102-
}
103-
104-
string displayName;
105-
106-
// Generic types
107-
if (type.Name.Contains('`'))
108-
{
109-
// Retrieve the current generic arguments for the current type (leaf or not)
110-
string[]? tokens = type.Name.Split('`');
111-
int genericArgumentsCount = int.Parse(tokens[1]);
112-
int typeArgumentsOffset = typeArguments.Length - genericTypeOffset - genericArgumentsCount;
113-
Type[]? currentTypeArguments = typeArguments.Slice(typeArgumentsOffset, genericArgumentsCount).ToArray();
114-
IEnumerable<string>? formattedTypes = currentTypeArguments.Select(t => FormatDisplayString(t, 0, t.GetGenericArguments()));
115-
116-
// Standard generic types are displayed as Foo<T>
117-
displayName = $"{tokens[0]}<{string.Join(", ", formattedTypes)}>";
118-
119-
// Track the current offset for the shared generic arguments list
120-
genericTypeOffset += genericArgumentsCount;
121-
}
122-
else
123-
{
124-
// Simple custom types
125-
displayName = type.Name;
126-
}
127-
128-
// If the type is nested, recursively format the hierarchy as well
129-
if (type.IsNested)
130-
{
131-
return $"{FormatDisplayString(type.DeclaringType!, genericTypeOffset, typeArguments)}.{displayName}";
132-
}
133-
134-
return $"{type.Namespace}.{displayName}";
135-
}
136-
13751
// Atomically get or build the display string for the current type.
13852
return DisplayNames.GetValue(type, t =>
13953
{
@@ -164,4 +78,117 @@ static string FormatDisplayString(Type type, int genericTypeOffset, ReadOnlySpan
16478
return FormatDisplayString(t, 0, t.GetGenericArguments());
16579
});
16680
}
81+
82+
/// <summary>
83+
/// Formats a given <see cref="Type"/> instance to its <see cref="string"/> representation.
84+
/// </summary>
85+
/// <param name="type">The input type.</param>
86+
/// <param name="genericTypeOffset">The offset into the generic type arguments for <paramref name="type"/>.</param>
87+
/// <param name="typeArguments">The generic type arguments for <paramref name="type"/>.</param>
88+
/// <returns>The <see cref="string"/> representation of <paramref name="type"/>.</returns>
89+
private static string FormatDisplayString(Type type, int genericTypeOffset, ReadOnlySpan<Type> typeArguments)
90+
{
91+
// Primitive types use the keyword name
92+
if (BuiltInTypesMap.TryGetValue(type, out string? typeName))
93+
{
94+
return typeName!;
95+
}
96+
97+
// Array types are displayed as Foo[]
98+
if (type.IsArray)
99+
{
100+
Type elementType = type.GetElementType()!;
101+
int rank = type.GetArrayRank();
102+
103+
return $"{FormatDisplayString(elementType, 0, elementType.GetGenericArguments())}[{new string(',', rank - 1)}]";
104+
}
105+
106+
// By checking generic types here we are only interested in specific cases,
107+
// ie. nullable value types or value tuples. We have a separate path for custom
108+
// generic types, as we can't rely on this API in that case, as it doesn't show
109+
// a difference between nested types that are themselves generic, or nested simple
110+
// types from a generic declaring type. To deal with that, we need to manually track
111+
// the offset within the array of generic arguments for the whole constructed type.
112+
if (type.IsGenericType)
113+
{
114+
Type genericTypeDefinition = type.GetGenericTypeDefinition();
115+
116+
// Nullable<T> types are displayed as T?
117+
if (genericTypeDefinition == typeof(Nullable<>))
118+
{
119+
Type[] nullableArguments = type.GetGenericArguments();
120+
121+
return $"{FormatDisplayString(nullableArguments[0], 0, nullableArguments)}?";
122+
}
123+
124+
// ValueTuple<T1, T2> types are displayed as (T1, T2)
125+
if (genericTypeDefinition == typeof(ValueTuple<>) ||
126+
genericTypeDefinition == typeof(ValueTuple<,>) ||
127+
genericTypeDefinition == typeof(ValueTuple<,,>) ||
128+
genericTypeDefinition == typeof(ValueTuple<,,,>) ||
129+
genericTypeDefinition == typeof(ValueTuple<,,,,>) ||
130+
genericTypeDefinition == typeof(ValueTuple<,,,,,>) ||
131+
genericTypeDefinition == typeof(ValueTuple<,,,,,,>) ||
132+
genericTypeDefinition == typeof(ValueTuple<,,,,,,,>))
133+
{
134+
IEnumerable<string> formattedTypes = FormatDisplayStringForAllTypes(type.GetGenericArguments());
135+
136+
return $"({string.Join(", ", formattedTypes)})";
137+
}
138+
}
139+
140+
string displayName;
141+
142+
// Generic types
143+
if (type.Name.AsSpan().IndexOf('`') != -1)
144+
{
145+
// Retrieve the current generic arguments for the current type (leaf or not)
146+
string[] tokens = type.Name.Split('`');
147+
int genericArgumentsCount = int.Parse(tokens[1]);
148+
int typeArgumentsOffset = typeArguments.Length - genericTypeOffset - genericArgumentsCount;
149+
Type[] currentTypeArguments = typeArguments.Slice(typeArgumentsOffset, genericArgumentsCount).ToArray();
150+
IEnumerable<string> formattedTypes = FormatDisplayStringForAllTypes(currentTypeArguments);
151+
152+
// Standard generic types are displayed as Foo<T>
153+
displayName = $"{tokens[0]}<{string.Join(", ", formattedTypes)}>";
154+
155+
// Track the current offset for the shared generic arguments list
156+
genericTypeOffset += genericArgumentsCount;
157+
}
158+
else
159+
{
160+
// Simple custom types
161+
displayName = type.Name;
162+
}
163+
164+
// If the type is nested, recursively format the hierarchy as well
165+
if (type.IsNested)
166+
{
167+
return $"{FormatDisplayString(type.DeclaringType!, genericTypeOffset, typeArguments)}.{displayName}";
168+
}
169+
170+
return $"{type.Namespace}.{displayName}";
171+
}
172+
173+
/// <summary>
174+
/// Formats a given sequence of <see cref="Type"/> instances to their <see cref="string"/> representations.
175+
/// </summary>
176+
/// <param name="types">The input types.</param>
177+
/// <returns>The <see cref="string"/> representations of <paramref name="types"/>.</returns>
178+
/// <remarks>
179+
/// <para>
180+
/// This method is explicitly an enumerator to avoid having to use LINQ in <see cref="FormatDisplayString"/>, which causes
181+
/// a noticeable binary size increase in AOT scenarios if reflection is enabled (which is the only supported mode).
182+
/// </para>
183+
/// <para>
184+
/// For future reference, see: <see href="https://github.com/dotnet/runtime/issues/82607#issuecomment-1444443656"/>.
185+
/// </para>
186+
/// </remarks>
187+
private static IEnumerable<string> FormatDisplayStringForAllTypes(Type[] types)
188+
{
189+
foreach (Type type in types)
190+
{
191+
yield return FormatDisplayString(type, 0, type.GetGenericArguments());
192+
}
193+
}
167194
}

0 commit comments

Comments
 (0)