DEV Community

JKC
JKC

Posted on

Stop Trimming \r Off Every Regex Match. .NET 11 Has AnyNewLine

TL;DR: In .NET 11, RegexOptions.AnyNewLine makes ^, $, \Z, and . treat \r\n, \r, and the Unicode line breaks as newlines, not just \n. No more \r?$, and no more values that end in an invisible carriage return. .NET 11 is at Release Candidate 2 right now: go-live licensed, but not final yet.

The character nobody can see

You write a regex that grabs a line. It passes every test, because the test strings were typed in C# with \n. Then a real file arrives from Windows, and every value you capture ends with \r.

using System.Text.RegularExpressions;

var bug = Regex.Match("foo\r\nbar", @".+$", RegexOptions.Multiline);
Console.WriteLine(bug.Value.Length); // 4
Enter fullscreen mode Exit fullscreen mode

Three letters, length 4. The fourth one is \r, and it only shows up later, when "foo" == value is mysteriously false.

The reason is old and simple. .NET's regex engine has always treated only \n as a newline. In Multiline mode, $ matches before \n, and . happily matches \r because, as far as the engine knows, it is just another character.

So everybody learned the ritual: [^\r\n]*\r?$. It looks like someone sneezed on the keyboard, and as you will see below, it is not even correct.

Meet AnyNewLine

.NET 11 adds one flag:

var fixedMatch = Regex.Match("foo\r\nbar", @".+$",
    RegexOptions.Multiline | RegexOptions.AnyNewLine);
Console.WriteLine(fixedMatch.Value.Length); // 3
Enter fullscreen mode Exit fullscreen mode

With it, ^, $, \Z, and . recognize all of these as line breaks: \r\n, \r, \n, \u0085 (NEL), 
 (line separator), and 
 (paragraph separator). Microsoft's docs say this follows Unicode TR18 RL1.6.

The important detail is that \r\n counts as one newline. In "a\r\nb", $ matched at positions 1 and 4: before the \r and at the end, never between \r and \n. ^ matched at 0 and 3, so the second line starts at b, not at a stray \n.

A realistic one: a config file from three operating systems

Here is a tiny key=value parser. The input mixes Windows, Unix, and classic Mac line endings, because somebody's editor had opinions.

using System.Text.RegularExpressions;

string config = "host=db.local\r\nport=5432\nuser=app\rtimeout=30\r\n";

foreach (Match m in Config.Line().Matches(config))
    Console.WriteLine($"{m.Groups["key"].Value} = [{m.Groups["value"].Value}]");

static partial class Config
{
    [GeneratedRegex(@"^(?<key>\w+)=(?<value>.*)$",
        RegexOptions.Multiline | RegexOptions.AnyNewLine)]
    public static partial Regex Line();
}
Enter fullscreen mode Exit fullscreen mode
host = [db.local]
port = [5432]
user = [app]
timeout = [30]
Enter fullscreen mode Exit fullscreen mode

Four keys, clean values. The source generator accepts the new flag, and so does RegexOptions.Compiled, which returned the same four values.

Now the same pattern without AnyNewLine. It found only three keys: host came back as db.local\r, and user swallowed the next line whole, as app\rtimeout=30\r. timeout vanished, because to the old engine a lone \r is not a line break at all.

And the ritual? ^(?<key>\w+)=(?<value>[^\r\n]*)\r?$ found 2 of the 4 keys. It fixes \r\n and quietly gives up on a bare \r.

Can I use it?

  • RegexOptions.AnyNewLine is new in .NET 11, with the value 2048. The .NET 11 Preview 3 release notes introduced it, and the RegexOptions docs list it as available in .NET 11 and later.
  • It works with new Regex(...), the static Regex methods, RegexOptions.Compiled, and [GeneratedRegex].
  • .NET 11 RC2 shipped on October 13, 2026 with a go-live support license. It is still a release candidate, and general availability is expected in November.
  • On the .NET 10 SDK, the code fails to build with CS0117: 'RegexOptions' does not contain a definition for 'AnyNewLine'.

I ran every snippet here as a file-based app with dotnet run file.cs on SDK 11.0.100-rc.1, on Windows.

Gotchas

1. NonBacktracking and ECMAScript say no. RegexOptions.AnyNewLine | RegexOptions.NonBacktracking threw ArgumentOutOfRangeException at construction, and so did the combination with ECMAScript. If you picked NonBacktracking for its linear-time guarantee, you keep \r? for now.

2. There is no inline letter. Multiline has (?m) and Singleline has (?s), but the docs list no inline option for AnyNewLine. You have to pass the flag in code, so a pattern stored in a config file cannot turn it on by itself.

3. Character classes are not newline-aware. The flag changes ^, $, \Z, and ., and nothing else. [^\n]+ against "foo\r\nbar" still matched 4 characters, \r included, with AnyNewLine on. If you want "anything but a line break", use . and let the flag do its job.

4. \A and \z do not move. They still mean the absolute start and end of the string. With AnyNewLine, done\Z matched "done\r\n", while done\z did not. That is by design, and it is the escape hatch when you really do mean "end of input".

5. Singleline still wins. With Singleline | AnyNewLine, a.+ against "a\r\nb" matched the whole string, newline included. Singleline means "the dot matches everything", and the new flag does not argue with it.

6. The docs disagree about \v and \f. The RegexOptions API reference says AnyNewLine also recognizes vertical tab and form feed. The options article lists only the six sequences above. In the RC1 run, . did not match \v or \f, and ^\w$ found 2 lines in "a\vb", so the API reference matches what the runtime did. If your input can contain form feeds, test it rather than trusting either page.

Wrap-up

\r has been hiding at the end of captured strings for as long as .NET has had regex. Now one flag tells the engine what a line actually is, on every OS at once. Go search your codebase for \r?$ and see how many of them can retire in .NET 11.

Sources

This article was written with the help of AI and checked against the sources above.

Top comments (0)