SearchValues<T>: Turbocharged String Scanning
Searching for characters in a string sounds like a solved problem, right? Just call IndexOfAny(char[]) and move on. But if you’re doing it in a parser, a tokenizer, or anything that processes megabytes of text, that naive approach leaves a lot of performance on the table. SearchValues<T> is .NET’s way of saying, “Give me your search set once, and I’ll vectorize the heck out of it.”
Here’s the basic setup: create a SearchValues<char> once and reuse it:
using System.Buffers;
// Create once, reuse everywherevar delimiters = SearchValues.Create([',', ';', '|', '\t', ' ']);
var input = "hello,world;this|is\ta test with spaces";
// Find the first delimitervar index = input.AsSpan().IndexOfAny(delimiters);Console.WriteLine($"First delimiter at index {index}: '{input[index]}'");
// Split manually using spans (zero allocation!)var remaining = input.AsSpan();while (remaining.Length > 0){ var delimIndex = remaining.IndexOfAny(delimiters); if (delimIndex < 0) { Console.WriteLine($"Token: \"{remaining}\""); break; }
Console.WriteLine($"Token: \"{remaining[..delimIndex]}\""); remaining = remaining[(delimIndex + 1)..];}When you pass a SearchValues<char> to IndexOfAny, the runtime picks the optimal SIMD strategy based on your hardware and the search set. Small sets might use a bitmap lookup. Larger sets might use AVX2 or ARM NEON vector comparisons. You don’t need to know the details; just know that it’s often 3-10x faster than passing a plain char[].
It also works with SearchValues<string> for multi-value string searching in .NET 9, which is fantastic for things like scanning for keywords or finding HTML tags:
var keywords = SearchValues.Create( ["async", "await", "Task", "ValueTask"], StringComparison.Ordinal);
var code = "The async method returns a Task<int> that you can await";var span = code.AsSpan();var pos = span.IndexOfAny(keywords);Console.WriteLine($"First keyword found at position {pos}: starts with '{span[pos..][..5]}'");The key insight is that SearchValues<T> is a precomputed search structure. The upfront cost of Create() is tiny, and every subsequent search benefits from the optimized representation. If you have any hot-path code doing IndexOfAny with a static set of characters or strings, this is the single easiest performance win you’ll find.