Regex in C# / .NET
.NET has the most feature-rich regex engine of any mainstream language. Includes lookarounds, balancing groups, conditionals, atomic groups, and Unicode properties.
The basics
using System.Text.RegularExpressions;
// Static methods — compile-on-demand and cache
bool ok = Regex.IsMatch(text, @"\d+");
Match m = Regex.Match(text, @"(\d+)-(\d+)");
MatchCollection all = Regex.Matches(text, @"\d+");
string result = Regex.Replace(text, @"\d+", "#");
// Or create a Regex instance for repeated use
var re = new Regex(@"(\d+)-(\d+)", RegexOptions.Compiled);
Match match = re.Match(text);
The @"..." verbatim string syntax lets you write backslashes naturally — no double-escaping needed.
RegexOptions.Compiled
By default .NET interprets regex bytecode. With RegexOptions.Compiled, it generates IL at first use — slower startup, faster steady-state. Use it for hot-path regexes used many times.
From .NET 7+, you can also use source generation with [GeneratedRegex]:
partial class Parser
{{
[GeneratedRegex(@"(\d+)-(\d+)")]
private static partial Regex DateRegex();
public void Parse(string text)
{{
Match m = DateRegex().Match(text);
}}
}}
Compiles the regex at build time. Fastest option for known patterns.
Named groups
.NET supports both (?<name>...) and (?'name'...):
var re = new Regex(@"(?<year>\d{{4}})-(?<month>\d{{2}})");
var m = re.Match("2024-06");
string year = m.Groups["year"].Value;
Group accessor returns a Group object. Success tells you if it participated; Captures contains all captures if the group was inside a quantifier (.NET tracks them all, unlike most engines).
Replacements with named groups
string result = Regex.Replace(
"first-last",
@"(?<first>\w+)-(?<last>\w+)",
"${{last}} ${{first}}"
);
// "last first"
Use ${{name}} in the replacement, not $<name>.
RegexOptions flags
RegexOptions.IgnoreCase
RegexOptions.Multiline
RegexOptions.Singleline // dot matches newline
RegexOptions.IgnorePatternWhitespace // verbose mode
RegexOptions.Compiled
RegexOptions.ECMAScript // JS-compatible matching semantics
RegexOptions.CultureInvariant
RegexOptions.RightToLeft // match RTL — useful for some HR/Arabic processing
RegexOptions.NonBacktracking // .NET 7+ — switch to RE2-like linear-time engine
The new NonBacktracking option is huge: opt into linear-time matching for any pattern that doesn't use back-references or atomic groups. Trades off some features for guaranteed performance.
Match timeout
.NET is unique in offering a per-match timeout, so a runaway regex can't hang your process:
var re = new Regex(@"(a+)+$", RegexOptions.None, TimeSpan.FromMilliseconds(100));
try
{{
re.IsMatch(input);
}}
catch (RegexMatchTimeoutException)
{{
// ReDoS detected, handled safely
}}
You can also set a process-wide default: AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromSeconds(1));.
Unique .NET features
Balancing groups — match nested parens or brackets in a single regex, something most engines can't do natively.
Variable-length lookbehind — most engines require fixed-width lookbehind. .NET allows arbitrary length.
Right-to-left matching — set RegexOptions.RightToLeft and the engine matches starting from the end. Useful for RTL languages and certain parsing tasks.
Conditional patterns — (?(1)yes|no) matches differently based on whether group 1 captured.
If you need any of these, .NET's the only mainstream engine that handles them out of the box.