.NET Regex Tester — C# System.Text.RegularExpressions, Live
Write patterns the way they appear in new Regex(...) or Regex.Match — named groups in either .NET spelling, leading inline flags, RegexOptions-shaped semantics — and the translator normalizes them for the evaluation engine, showing each rewrite. Constructs that cannot travel, like balancing groups and scoped (?i:...), are rejected with precise reasons.
Zero upload, ever
Patterns and test data are evaluated in your browser — connection strings and column values never touch a server.
Live translation
The dialect translator and the match engine recompute on every keystroke, with notices explaining each rewrite.
Honest where it matters
Constructs that cannot be translated faithfully are rejected with a precise reason — never silently mis-matched.
Test string
Matches highlight as you type
Match results
Sandboxed Web WorkerType a pattern to see live matches.
.NET regular expressions, in depth
Engine: System.Text.RegularExpressions.Regex (.NET 6/7/8). The tester translates your pattern for live evaluation — these sections document how .NET's engine really behaves and where it differs from the evaluation engine.
Regex, Regex.Match, and the shape of everyday .NET code
.NET regex usage clusters around a few idioms: the static Regex.Match(subject, pattern) and Regex.Matches(...) for one-off calls, instance regexes for reuse, and — since .NET 7 — the generated source generator ([GeneratedRegex(...)]) that compiles the pattern at build time. The tester's global mode is Regex.Matches(): one row per Match in the returned MatchCollection, with named groups per row exactly like match.Groups["name"].Value. Single-match mode is Regex.Match(); Success is the tester's 'no matches found' state.
The APIs the tester cannot simulate are still worth planning around. .NET's Regex supports a match timeout (TimeSpan) precisely because catastrophic backtracking takes down services — the same threat model this tester enforces with its two-second worker budget and Catastrophic Backtracking Detected report. RegexOptions.IgnoreCase, Multiline, and Singleline map to the i, m, and s flags on the flag bar; IgnorePatternWhitespace is .NET's verbose mode and, like PCRE's x, is rejected rather than approximated. If your production pattern carries a timeout, treat a tester rejection as your timeout firing early.
Named groups in both spellings — and the balancing-group wall
.NET accepts two named-group spellings — (?<name>...) and (?'name'...) — and the translator normalizes both to the form JavaScript shares. Named groups are the backbone of the nicest .NET regex feature set: match.Groups["name"], ${name} substitution in Regex.Replace, and named capture in routed templates. The tester's group panel speaks the same vocabulary, so a pattern validated here carries its names straight into production code. One spelling caution: a hyphenated name like (?<a-b>...) is not a name at all — it is a balancing group definition, and it is rejected with that exact explanation.
Balancing groups — (?<'Before-After'>...) — are .NET's mechanism for matching nested balanced constructs (every open paren has its close), built on the stack discipline of previous captures. They are genuinely useful and genuinely untranslatable: no other mainstream engine has them. The honest rejection message points at the alternatives that do travel — parser combinators, recursive descent, or counter-based matching in code — which is the same advice the .NET team gives for expression-sized problems that outgrow regex.
Inline flags, RegexOptions, and Unicode defaults
.NET allows inline flag letters anywhere in a pattern and can scope them with (?i:...) groups — a subexpression that is case-insensitive while the rest of the pattern stays sensitive. JavaScript supports flags only whole-pattern, so the translator promotes leading (?i), (?m), and (?s) to flags and rejects scoped or mid-pattern forms with an explanation, because dropping the scope would change what matches. The negative forms (?-i) and composite (?im-sx) are rejected for the same reason: turning flags off midway is not a thing JavaScript can do.
The Unicode defaults run opposite to Java's: .NET's \d, \w, and \s are Unicode-aware unless RegexOptions.ECMAScript narrows them, while JavaScript's are ASCII unless u widens them. The translator attaches a notice to every shorthand class recommending the u flag for parity, and that notice is not decoration — on internationalized data, ASCII-only matching silently skips rows. General categories and scripts (\p{L}, \p{IsGreek} on the .NET side versus \p{Script=Greek} in JavaScript) pass through where the vocabularies overlap, and the Is-block escapes .NET uses are rejected where they do not.
Anchors, substitutions, and the $<newline> corner
The anchor map is the one to keep close. .NET's \A is absolute start (JavaScript's ^ with m off); .NET's \z is absolute end; .NET's \Z matches before a final newline — which is exactly JavaScript's $ with m off. The translator maps \A, \z, and \Z onto ^ and $ with a notice documenting which corner case differs: JavaScript's $ matching before one trailing newline. In .NET, $ without RegexOptions.Multiline matches at the end of the string or before a final newline — the same shape — so multiline workflows map cleanly: .NET MultILINE is JavaScript m, anchoring ^ and $ per line in both.
Replacement strings round out the portability story: .NET's ${name} and $1 substitutions in Regex.Replace map to JavaScript's $<name> and $1 in String.replace(), with the same capture numbering the group panel shows. .NET's substitution extras — $$ for a literal dollar, $& for the whole match — have JavaScript twins with the same spelling. The one substitution API without a twin is MatchEvaluator (the lambda form of Regex.Replace); its equivalent is JavaScript's replace with a function callback, which is the same idea in different clothes.
.NET versus the evaluation engine
What translates automatically, what carries a caveat, and what is rejected — construct by construct.
| Construct | .NET | This tester |
|---|---|---|
| Named groups | (?<name>...) and (?'name'...) | (?<name>...) — both spellings normalized |
| Balancing groups | (?<a-b>...) match nested balanced constructs | No equivalent — rejected with guidance |
| Inline flags | Anywhere, scoped with (?i:...), negative with (?-i) | Whole-pattern only; leading forms promoted, scoped rejected |
| Shorthand classes | \d \w \s are Unicode by default | ASCII by default; u flag needed for parity — flagged |
| String end | \z absolute; \Z before final newline; $ like \Z | $ (no m) behaves like \Z — caveat attached |
| Timeouts | Regex(...) accepts a TimeSpan match timeout | This tester's 2-second worker budget is the equivalent guard |
| Substitutions | ${name}, $1, $&, $$ in Regex.Replace | $<name>, $1, $&, $$ in String.replace() |
| Unicode blocks | \p{IsBasicLatin} and Is-block escapes | Scripts and categories instead — \p{Script=Latin} with u |
.NET regex cheat sheet
The .NET-specific tokens, with the constructs that cannot be translated marked explicitly.
| Token | Meaning | Example |
|---|---|---|
| (?<name>…) | Named group (.NET spelling #1) | (?<area>\d{3}) |
| (?'name'…) | Named group (.NET spelling #2) | (?'line'\d{4}) |
| (?<a-b>…) | Balancing group — not translatable | blocked with a reason |
| (?i) (?m) (?s) | Inline flags at pattern start | (?i)error → ERROR |
| (?i:…) | Scoped inline flag — not translatable | blocked with a reason |
| \A \z \Z | Start / absolute end / end before final newline | \A\d+\z |
| \d \w \s | Unicode by default in .NET | \w+ → naïve OK |
| ${name} | Named substitution in Regex.Replace | ${area}-${line} |
| $& | Whole match in substitution | "$&!" |
| TimeSpan | Match timeout — echoed by the 2s ReDoS guard here | new Regex(p, opts, TimeSpan.FromSeconds(1)) |
Frequently asked questions
Why is my balancing group rejected?
Balancing groups — (?'Open-Close'...) or (?<Open-Close>...) — maintain a capture stack and are .NET's mechanism for matching nested balanced text like parentheses or XML tags. No other mainstream engine has them, so there is no translation that preserves meaning; the rejection names the construct. For nested structures, use a parser (or counter-based scanning in code) — the same direction the .NET documentation itself points when regex outgrows the problem.
Does (?i) work the same here as in C#?
At the start of a pattern, yes — the translator promotes (?i) to the i flag and notes it. The .NET-only superpowers are rejected: scoped groups like (?i:subexpression), mid-pattern flags, and negative forms like (?-i) cannot be expressed whole-pattern in JavaScript, and approximating them would change what matches. If you need scoped case-insensitivity, split the pattern so the insensitive part is its own pattern, or restructure with explicit [Aa] classes.
Why does \w here only match ASCII until I add u?
Because the engines' defaults are opposite: .NET's \d, \w, and \s are Unicode-aware unless RegexOptions.ECMAScript narrows them, while JavaScript's are ASCII-only unless the u flag widens them. The translator attaches a notice recommending u for parity, and with u on, the classes cover code-point text close to .NET's behavior. Test with the actual international characters you expect — that is the only way to see which side of the default your data lands on.
How do the 2-second budget and .NET's Regex timeout compare?
They exist for the same reason. .NET lets you pass a TimeSpan to new Regex(pattern, options, timeout) — or set Regex.MatchTimeout app-wide — so a catastrophic pattern throws RegexMatchTimeoutException instead of hanging a thread. This tester enforces the same discipline client-side: evaluation runs in a Web Worker with a two-second budget, and exceeding it reports Catastrophic Backtracking Detected with the offending shape. A pattern rejected here is a pattern that would burn your .NET timeout too — fix the shape, not the timeout.
Can I test RegexOptions.IgnorePatternWhitespace patterns?
No — verbose mode is rejected, because ignoring whitespace and # comments inside the pattern is a semantic transformation a translator cannot perform safely (which whitespace is ignored changes what matches). Write the pattern without internal whitespace and comments here. In C# code, keep using IgnorePatternWhitespace for readability — then test the de-commented, de-whitespaced form here, since that is what the engine ultimately compiles anyway.
How do I map this tester's groups to C# code?
The group panel is match.Groups["name"].Value for named groups and match.Groups[i] for numbered ones; the offsets column is match.Index and match.Index + match.Length. Global mode is Regex.Matches() iterating a MatchCollection; single-match mode is Regex.Match(). Substitutions translate directly: ${name} becomes $<name> and $& stays $& when you move from Regex.Replace to JavaScript's replace — same captures, same values.