using System;
using System.Collections.Generic;
using System.Linq;
public static class TokenInternDemo
{
public static void Main()
{
// new string(...) binds to the string(ReadOnlySpan<char>) constructor, so each of these
// is a fresh instance built at run time, not the compiler's interned literal.
var tokens = new[] { "GET", new string("POST"), new string("GET"), "PUT", new string("GET") };
// ReferenceEqualityComparer has been in System.Collections.Generic since .NET 5.
// Do not hand-roll one: a home-made comparer that compares references but hashes
// contents is inconsistent by construction, and the name shadows the BCL type.
int before = tokens.Distinct(ReferenceEqualityComparer.Instance).Count();
for (int i = 0; i < tokens.Length; i++)
tokens[i] = string.Intern(tokens[i]);
int after = tokens.Distinct(ReferenceEqualityComparer.Instance).Count();
Console.WriteLine($"Before refs: {before}, After refs: {after}");
Console.WriteLine(object.ReferenceEquals(tokens[0], tokens[2]));
Console.WriteLine(object.ReferenceEquals(tokens[0], tokens[4]));
Console.WriteLine(string.IsInterned(new string("GET")) is not null);
Console.WriteLine(string.IsInterned(Guid.NewGuid().ToString()) is null);
}
}
Output (.NET 10, deterministic):
Before refs: 5, After refs: 3 True True True True
Five values, five distinct references before interning and three after: the three "GET" entries collapse onto one instance, which is the literal the compiler already placed in the pool. The last two lines are the reason the exercise insists on runtime-built strings: a value equal to a live literal is found in the pool, and a fresh high-cardinality value is not, which is exactly why you never intern user input.