C# string interning is a performance optimization that can significantly speed up string comparisons and reduce memory usage by ensuring that only one copy of a given string value exists in memory.
Let’s see how this plays out with some code. Imagine we have a loop that creates many identical strings:
using System;
using System.Collections.Generic;
using System.Diagnostics;
public class StringDemo
{
public static void Main(string[] args)
{
// Scenario 1: Without interning
Console.WriteLine("--- Without Interning ---");
var stringsWithoutInterning = new List<string>();
var sw1 = Stopwatch.StartNew();
for (int i = 0; i < 100000; i++)
{
stringsWithoutInterning.Add($"ID-{i % 1000}"); // Many duplicate strings
}
sw1.Stop();
Console.WriteLine($"Time taken: {sw1.ElapsedMilliseconds} ms");
Console.WriteLine($"List count: {stringsWithoutInterning.Count}");
Console.WriteLine($"Memory allocated (approx): {Process.GetCurrentProcess().PrivateMemorySize64 / (1024 * 1024)} MB");
Console.WriteLine("\n--- With Interning ---");
// Scenario 2: With interning
var stringsWithInterning = new List<string>();
var sw2 = Stopwatch.StartNew();
for (int i = 0; i < 100000; i++)
{
stringsWithInterning.Add(string.Intern($"ID-{i % 1000}")); // Using string.Intern
}
sw2.Stop();
Console.WriteLine($"Time taken: {sw2.ElapsedMilliseconds} ms");
Console.WriteLine($"List count: {stringsWithInterning.Count}");
Console.WriteLine($"Memory allocated (approx): {Process.GetCurrentProcess().PrivateMemorySize64 / (1024 * 1024)} MB");
Console.WriteLine("\n--- StringBuilder Example ---");
// Scenario 3: Using StringBuilder for repetitive concatenation
var sb = new System.Text.StringBuilder();
var sw3 = Stopwatch.StartNew();
for (int i = 0; i < 50000; i++)
{
sb.Append("Item_");
sb.Append(i);
sb.Append("_Details");
sb.AppendLine(); // Add a newline for clarity in output
}
string finalString = sb.ToString();
sw3.Stop();
Console.WriteLine($"StringBuilder time taken: {sw3.ElapsedMilliseconds} ms");
Console.WriteLine($"Final string length: {finalString.Length}");
}
}
When you run this code, you’ll observe that the "With Interning" scenario is significantly faster and consumes less memory. The string.Intern() method checks if a string with the same value already exists in the "intern pool." If it does, it returns a reference to the existing string; otherwise, it adds the new string to the pool and returns a reference to it. This means that all identical strings (like "ID-123") will point to the same memory location, avoiding redundant allocations and speeding up comparisons because they can be done by simply comparing memory addresses.
The problem StringBuilder solves is the inefficiency of repeated string concatenation using the + or += operators. Each time you use + or += with strings, a new string object is created in memory. This is because strings in C# are immutable. For a loop with many concatenations, this leads to a cascade of temporary string objects being created and then discarded by the garbage collector, which is a significant performance drain and memory overhead. StringBuilder, on the other hand, uses a mutable internal buffer. It appends characters or strings to this buffer, resizing it as needed, but it doesn’t create a new string object until you explicitly call ToString(). This drastically reduces the number of object allocations and garbage collection pressure.
The key levers you control are:
string.Intern(string str): Explicitly adds a string to the interning pool or retrieves an existing one. Use this when you know you’ll have many duplicate string literals or strings generated from a predictable set of values.string.IsInterned(string str): Checks if a string is currently in the interning pool without adding it.StringBuildercapacity: You can pre-set the initial capacity of aStringBuilderto avoid frequent reallocations if you have an estimate of the final string’s length.new StringBuilder(capacity)StringBuilder.Append()methods: The various overloads allow appending different data types efficiently.StringBuilder.ToString(): This is the point where the final, immutable string is created.
A common misconception is that string.Intern() should be used everywhere. It’s crucial to understand that interning has a cost: adding strings to the pool takes time, and the pool itself consumes memory. Over-interning, especially with unique or rarely duplicated strings, can actually decrease performance and increase memory usage. The benefit is realized when there’s a high degree of string duplication. For example, if you’re parsing a large file where certain words or phrases repeat thousands of times, interning those specific strings can yield substantial gains. Similarly, if you’re building a very long string piece by piece, StringBuilder is almost always the correct choice over repeated + operations.
The next optimization you’ll likely encounter involves understanding how string comparisons work at a lower level, particularly the difference between ordinal and culture-sensitive comparisons.