Question
In C#, I tested three regular expression patterns for detecting whether a string contains a digit:
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace SO_RegexPerformance
{
class Program
{
static void Main(string[] args)
{
var rand = new Random(1234);
var strings = new List<string>();
// Generate 10,000 random strings
for (var i = 0; i < 10000; i++)
{
var sb = new StringBuilder();
for (var c = 0; c < 1000; c++)
{
sb.Append((char)('a' + rand.Next(26)));
}
// Add a digit to about half of the strings
if (rand.Next(2) == 0)
{
sb[rand.Next(sb.Length)] = (char)('0' + rand.Next(10));
}
strings.Add(sb.ToString());
}
var baseTime = TestPerformance(strings, @"\d");
Console.WriteLine();
var testTime = TestPerformance(strings, "[0-9]");
Console.WriteLine(" {0:P2} of first", testTime.TotalMilliseconds / baseTime.TotalMilliseconds);
testTime = TestPerformance(strings, "[0123456789]");
Console.WriteLine(" {0:P2} of first", testTime.TotalMilliseconds / baseTime.TotalMilliseconds);
}
private static TimeSpan TestPerformance(List<string> strings, string regex)
{
var sw = new Stopwatch();
int successes = 0;
var rex = new Regex(regex);
sw.Start();
foreach (var str in strings)
{
if (rex.Match(str).Success)
{
successes++;
}
}
sw.Stop();
Console.Write("Regex {0,-12} took {1} result: {2}/{3}", regex, sw.Elapsed, successes, strings.Count);
return sw.Elapsed;
}
}
}
The results showed that \d was slower than both [0-9] and [0123456789], while the latter two performed similarly.
Why does this happen?
- I expected
[0-9]to be more efficient than[0123456789]. - I also expected
\dto behave like shorthand for[0-9], but it appears to do more work.
What is the difference between \d and [0-9] in the C# regex engine, and why might \d be slower?
Short Answer
By the end of this page, you will understand why \d and [0-9] are not always equivalent in C# regular expressions, how Unicode character classes affect performance, and why benchmark results can differ even when patterns look similar. You will also learn when to prefer readability, correctness, or raw speed.
Concept
In C# regular expressions, \d is not always the same thing as [0-9].
[0-9]means: match one ASCII character in the range from'0'to'9'.[0123456789]means exactly the same set of ASCII characters, just written out explicitly.\dmeans: match a digit character class.
In .NET regex, \d is Unicode-aware by default. That means it can match more than just ASCII digits. It may also match other characters considered digits in Unicode, depending on the regex engine rules in use.
Because of that, \d can require a more general classification check than [0-9], which only needs a simple range comparison.
Why this matters
When you write regex, you are choosing between:
- meaning: what characters should match
- portability: whether behavior is consistent across systems
- performance: how much work the engine must do
If you only want ASCII digits, [0-9] is often the clearest and fastest choice.
If you want any Unicode digit recognized by .NET, \d is more correct.
Why [0-9] and [0123456789] are similar
Regex engines usually optimize character classes internally. Even if you write all ten digits explicitly, the engine may compile that set into an efficient internal representation. So the difference between a range and an explicit set may be negligible.
Why \d may be slower
A typical reason is that the engine must ask something like:
- “Is this character in the Unicode digit category?”
instead of just checking:
- “Is this character between
'0'and'9'?”
That extra flexibility can cost time, especially when repeated across many characters in many strings.
Mental Model
Think of these patterns like ways of checking IDs at a door:
[0-9]is like saying: only let in people wearing badges numbered 0 through 9.[0123456789]is like keeping a printed list of the same ten badge numbers.\dis like saying: let in anyone who counts as a digit according to the full international rulebook.
The first two checks are very simple. The third check may require consulting a larger set of rules.
So \d is more flexible, but flexibility can mean extra work.
Syntax and Examples
Core syntax
var asciiRange = new Regex("[0-9]");
var explicitSet = new Regex("[0123456789]");
var digitClass = new Regex(@"\d");
Basic example
using System;
using System.Text.RegularExpressions;
class Demo
{
static void Main()
{
string input = "Room 7";
Console.WriteLine(Regex.IsMatch(input, "[0-9]")); // True
Console.WriteLine(Regex.IsMatch(input, "[0123456789]")); // True
Console.WriteLine(Regex.IsMatch(input, @"\d")); // True
}
}
All three patterns match 7 here.
Important difference in meaning
using System;
using System.Text.RegularExpressions;
class Demo
{
static void Main()
{
string arabicIndicDigit = "\u0661"; // Arabic-Indic digit one
Console.WriteLine(Regex.IsMatch(arabicIndicDigit, "[0-9]")); // False
Console.WriteLine(Regex.IsMatch(arabicIndicDigit, @"\d")); // Often True in .NET
}
}
This is the key idea:
[0-9]only matches ASCII digits.\dcan match a broader set of Unicode digits.
If you want ASCII-only shorthand-like behavior
In many codebases, developers simply use [0-9] when they want to be explicit.
bool hasAsciiDigit = Regex.IsMatch("A1B", "[0-9]");
That removes ambiguity about what counts as a digit.
Step by Step Execution
Example
using System;
using System.Text.RegularExpressions;
class Demo
{
static void Main()
{
string input = "abc5xyz";
var regex = new Regex("[0-9]");
bool found = regex.IsMatch(input);
Console.WriteLine(found);
}
}
What happens step by step
inputis set to"abc5xyz".- The regex pattern
[0-9]is compiled into a rule that checks whether a character is between'0'and'9'. IsMatchstarts scanning from the beginning of the string.- It checks
'a':- not in the range
0to9
- not in the range
- It checks
'b':- not in the range
0to9
- not in the range
- It checks
'c':- not in the range
0to9
- not in the range
- It checks
'5':- this is in the range
0to9
- this is in the range
- The engine stops early because it found a match.
foundbecomestrue.Trueis printed.
Same idea with \d
var regex = new Regex(@"\d");
The scanning process is similar, but the character test may be more complex because the engine may use Unicode digit classification instead of a simple ASCII range check.
Real World Use Cases
When [0-9] is a good choice
Use [0-9] when your input format is explicitly ASCII-based:
- ZIP codes like
12345 - invoice numbers with Western digits
- version strings such as
v2 - parsing machine-generated logs
- validating numeric IDs from systems that only emit ASCII
Example:
bool containsAsciiDigit = Regex.IsMatch("OrderA7", "[0-9]");
When \d is a better choice
Use \d when you want to accept digits more generally under Unicode rules:
- internationalized user input
- apps that support multiple scripts
- text processing where digit characters may come from different writing systems
Example:
bool containsAnyDigit = Regex.IsMatch(userInput, @"\d");
When performance matters
In hot paths such as:
- processing millions of records
- scanning large log files
- validating high-throughput API input
small regex differences can matter. In those cases, benchmark the exact scenario and choose the narrowest pattern that matches your real requirements.
Real Codebase Usage
In real projects, developers usually choose patterns based on intent first and performance second.
Common patterns
Explicit validation
If a field must contain ASCII digits only, teams often prefer [0-9] because it is unambiguous.
bool isValidProductCode = Regex.IsMatch(code, "^[0-9]+$");
Guard clauses
Regex is often used early in a method to reject invalid input.
if (!Regex.IsMatch(id, "^[0-9]+$"))
{
throw new ArgumentException("ID must contain ASCII digits only.");
}
Configuration and parsing
Developers often use simple classes instead of broad shorthand when parsing config files or protocol formats.
var match = Regex.Match(line, "port=([0-9]+)");
Error handling and domain rules
If an application must support localized input, \d may be the right semantic choice.
if (!Regex.IsMatch(input, @"^\d+$"))
{
return "Please enter digits.";
}
Practical advice
- Use
[0-9]when you mean ASCII digits only. - Use
\dwhen you mean Unicode digits. - Do not assume shorthand and explicit classes are identical across regex engines.
Common Mistakes
1. Assuming \d always means [0-9]
This is one of the most common mistakes.
Regex.IsMatch("\u0661", @"\d"); // may be True
Regex.IsMatch("\u0661", "[0-9]"); // False
How to avoid it
Decide whether you want:
- ASCII digits only: use
[0-9] - Unicode digits: use
\d
2. Benchmarking too little work
Very small benchmarks can be noisy because of:
- JIT compilation
- memory allocation
- CPU cache effects
- regex startup cost
How to avoid it
- warm up the code first
- run multiple iterations
- isolate regex construction from matching
- use a proper benchmarking tool when possible
3. Recreating regex objects unnecessarily
Broken approach:
foreach (var str in strings)
{
var regex = new Regex("[0-9]");
if (regex.IsMatch(str))
{
// ...
}
}
This repeatedly constructs the regex.
Better:
var regex = new Regex("[0-9]");
foreach (var str in strings)
{
if (regex.IsMatch(str))
{
// ...
}
}
4. Using regex when a simple character check would do
If you only need to know whether a string contains an ASCII digit, regex may be overkill.
bool hasDigit = false;
foreach (char ch in input)
{
if (ch >= '0' && ch <= '9')
{
hasDigit = true;
break;
}
}
How to avoid it
Use regex when pattern matching is the right tool, not automatically.
5. Confusing readability with performance
Some developers assume shorter syntax is always faster. That is not guaranteed.
\dis shorter[0-9]is narrower in meaning- either may be better depending on the requirement
Comparisons
| Pattern | Meaning in C# regex | Typical scope | Performance tendency | Best use |
|---|---|---|---|---|
\d | Digit character class | Unicode-aware digit matching | May be slower than ASCII range checks | Internationalized digit matching |
[0-9] | ASCII digits 0 through 9 | Narrow and explicit | Often very fast | ASCII-only validation |
[0123456789] | Explicit list of ASCII digits | Same practical meaning as [0-9] | Usually similar to [0-9] | Rarely needed unless you prefer explicit listing |
[0-9] vs [0123456789]
These usually behave the same for matching ASCII digits. Most regex engines optimize both forms, so performance differences are often tiny.
\d vs [0-9]
The key difference is semantics:
\d= digit class[0-9]= ASCII range only
If semantics differ, performance can differ too.
Regex vs manual character scan
| Approach | Pros | Cons |
|---|---|---|
| Regex | Expressive, concise, easy to extend | More overhead |
| Manual scan | Often fastest for simple checks | Less flexible for complex patterns |
For a simple “contains a digit” check, manual scanning can be simpler and faster than regex.
Cheat Sheet
Quick reference
Digit-related patterns in C# regex
@"\d" // digit character class, Unicode-aware
"[0-9]" // ASCII digits only
"[0123456789]" // explicit ASCII digit set
Key rule
\dis not always equivalent to[0-9]in .NET.
Choose this when...
- Use
\dwhen you want Unicode digits. - Use
[0-9]when you want ASCII digits only.
Performance notes
[0-9]may be faster because it is a simple range check.[0123456789]is often optimized similarly to[0-9].\dmay do broader character classification.
Common examples
Regex.IsMatch("abc5", "[0-9]") // True
Regex.IsMatch("abc5", @"\d") // True
Regex.IsMatch("\u0661", "[0-9]") // False
Regex.IsMatch("\u0661", @"\d") // Often True
Best practice
Write the pattern that matches your actual requirement, then benchmark if performance matters.
FAQ
Why is \d slower than [0-9] in C#?
Because \d usually means a broader Unicode digit class, while [0-9] is only a simple ASCII range check.
Is [0123456789] slower than [0-9]?
Usually not by much. Regex engines often optimize both patterns into similar internal checks.
Does \d always match only 0 through 9?
No. In .NET, \d is generally Unicode-aware and can match digit characters beyond ASCII 0 to 9.
Which should I use: \d or [0-9]?
Use [0-9] for ASCII-only input. Use \d when you want to support Unicode digits.
Is regex the fastest way to check whether a string contains a digit?
Not always. For simple checks, a manual character loop can be faster and easier to reason about.
Why do benchmark results vary between runs?
Because of JIT compilation, CPU state, memory effects, and measurement noise. Small timing differences are not always meaningful.
Should I optimize regex syntax first?
Only if profiling shows regex is a bottleneck. Correctness and clarity usually matter more than tiny performance gains.
Mini Project
Description
Build a small C# console program that checks whether input strings contain ASCII digits or Unicode digits, then compares the behavior of [0-9] and \d. This project helps you see that the two patterns can produce different results, which is important in validation and internationalized applications.
Goal
Create a program that tests multiple strings against both [0-9] and \d and prints which pattern matches each input.
Requirements
- Create a list of sample strings, including one with ASCII digits and one with a Unicode digit.
- Test each string using both
[0-9]and\d. - Print the input and whether each regex matched.
- Include at least one example where the two patterns produce different results.
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
Best Way to Repeat a Character in C#: Building Repeated Strings Efficiently
Learn the best way to repeat a character in C#, compare StringBuilder, string concatenation, and simpler built-in options.
C# Access Modifiers and static: public, private, protected, and Defaults
Learn how C# public, private, protected, and default access control visibility, and how static differs from instance members.