From 626ba558ad45019d75558eaa22500ffc5c6ff62d Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Mon, 7 Sep 2026 13:26:41 +0200 Subject: [PATCH] Replace line breaks: retarget net10.0, fix the regex, real mixed-ending fixture - All three projects retargeted net7.0 -> net10.0. - Regex: @"(\r\n|\r)" never named \n, so the sample silently ignored Unix line endings, which is the most common text a .NET application reads. Fixed to \r\n|\r|\n and the unused capturing group dropped. Converted to a [GeneratedRegex] partial method, which is the current idiom and also takes the static-cache lookup out of the benchmarked path. - Fixture: "This is a line.\rThis is another line." carried a single carriage return and neither of the other two common sequences, so no method in the sample exercised what the article is about. Now "Line one.\r\nLine two.\nLine three.\rLine four." Each method gains a string-taking overload so the benchmark can drive two inputs; the parameterless ones keep the fixture. - New RemoveLineBreaks() backing the removal case, via ReplaceLineEndings(""). - Tests: three expectations follow the new fixture, plus three new cases - the removal method, the CRLF ordering trap (replacing \r before \r\n doubles every Windows break), and a U+2028 line separator that ReplaceLineEndings() matches and the Replace() chain does not. 6/6 green on net10.0. - Benchmarks: second input added, a paragraph carrying \r\n, \n and \r mixed, with the old short string kept as a labelled case. - Packages: BenchmarkDotNet 0.15.8, Microsoft.NET.Test.Sdk 18.9.0, xunit 2.9.3, xunit.runner.visualstudio 4.0.0, coverlet.collector 10.0.1. --- .../Program.cs | 3 ++ .../ReplaceLineBreak.cs | 53 ++++++++++--------- .../ReplaceLineBreaksInAStringCSharp.csproj | 2 +- .../ReplaceLineBreakBenchmarks.cs | 35 ++++++++++-- ...LineBreaksInAStringCSharpBenchmarks.csproj | 4 +- .../ReplaceLineBreakTests.cs | 42 +++++++++++++-- ...placeLineBreaksInAStringCSharpTests.csproj | 10 ++-- 7 files changed, 108 insertions(+), 41 deletions(-) diff --git a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp/Program.cs b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp/Program.cs index 2a7173f40b..804135e914 100644 --- a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp/Program.cs +++ b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp/Program.cs @@ -8,3 +8,6 @@ var regularExpressionReplaceResult = ReplaceLineBreak.ReplaceLineBreaksUsingTheRegularExpressionReplaceMethod(); Console.WriteLine(regularExpressionReplaceResult); + +var removeLineBreaksResult = ReplaceLineBreak.RemoveLineBreaks(); +Console.WriteLine(removeLineBreaksResult); diff --git a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreak.cs b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreak.cs index fc5b187459..c8326595f8 100644 --- a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreak.cs +++ b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreak.cs @@ -2,29 +2,34 @@ namespace ReplaceLineBreaksInAStringCSharp; -public static class ReplaceLineBreak +public static partial class ReplaceLineBreak { - public static string ReplaceLineBreaksUsingTheStringReplaceMethod() - { - const string text = "This is a line.\rThis is another line."; - var newText = text.Replace("\r\n", "\n").Replace("\r", "\n"); - - return newText; - } - - public static string ReplaceLineBreaksUsingTheStringReplaceLineEndingsMethod() - { - const string text = "This is a line.\rThis is another line."; - var newText = text.ReplaceLineEndings("\n"); - - return newText; - } - - public static string ReplaceLineBreaksUsingTheRegularExpressionReplaceMethod() - { - const string text = "This is a line.\rThis is another line."; - var newText = Regex.Replace(text, @"(\r\n|\r)", "\n"); - - return newText; - } + public const string Text = "Line one.\r\nLine two.\nLine three.\rLine four."; + + public static string ReplaceLineBreaksUsingTheStringReplaceMethod() => + ReplaceLineBreaksUsingTheStringReplaceMethod(Text); + + public static string ReplaceLineBreaksUsingTheStringReplaceMethod(string text) => + text.Replace("\r\n", "\n").Replace("\r", "\n"); + + public static string ReplaceLineBreaksUsingTheStringReplaceLineEndingsMethod() => + ReplaceLineBreaksUsingTheStringReplaceLineEndingsMethod(Text); + + public static string ReplaceLineBreaksUsingTheStringReplaceLineEndingsMethod(string text) => + text.ReplaceLineEndings("\n"); + + public static string ReplaceLineBreaksUsingTheRegularExpressionReplaceMethod() => + ReplaceLineBreaksUsingTheRegularExpressionReplaceMethod(Text); + + public static string ReplaceLineBreaksUsingTheRegularExpressionReplaceMethod(string text) => + LineBreakRegex().Replace(text, "\n"); + + public static string RemoveLineBreaks() => + RemoveLineBreaks(Text); + + public static string RemoveLineBreaks(string text) => + text.ReplaceLineEndings(string.Empty); + + [GeneratedRegex(@"\r\n|\r|\n")] + private static partial Regex LineBreakRegex(); } diff --git a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp.csproj b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp.csproj index f02677bf64..dfb40caafc 100644 --- a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp.csproj +++ b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharp.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net10.0 enable enable diff --git a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpBenchmarks/ReplaceLineBreakBenchmarks.cs b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpBenchmarks/ReplaceLineBreakBenchmarks.cs index 7edef60f35..80f6966b2d 100644 --- a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpBenchmarks/ReplaceLineBreakBenchmarks.cs +++ b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpBenchmarks/ReplaceLineBreakBenchmarks.cs @@ -4,14 +4,41 @@ namespace ReplaceLineBreaksInAStringCSharpBenchmarks; [MemoryDiagnoser] -public class ReplaceLineBreakBenchmarks +public class ReplaceLineBreakBenchmarks { + private const string ShortText = "This is a line.\rThis is another line."; + + private const string MixedText = + "The quick brown fox jumps over the lazy dog.\r\n" + + "Pack my box with five dozen liquor jugs.\n" + + "How vexingly quick daft zebras jump.\r" + + "Sphinx of black quartz, judge my vow.\r\n" + + "Jackdaws love my big sphinx of quartz.\n" + + "The five boxing wizards jump quickly.\r" + + "Bright vixens jump; dozy fowl quack.\r\n" + + "Quick zephyrs blow, vexing daft Jim.\n" + + "Two driven jocks help fax my big quiz.\r" + + "Waltz, bad nymph, for quick jigs vex.\r\n" + + "Glib jocks quiz nymph to vex dwarf.\n" + + "Fickle jinx bog dwarves spy math quiz.\r"; + + [Params("Short", "Mixed")] + public string Input { get; set; } = "Short"; + + private string _text = ShortText; + + [GlobalSetup] + public void GlobalSetup() => _text = Input == "Short" ? ShortText : MixedText; + [Benchmark] - public string StringReplace() => ReplaceLineBreak.ReplaceLineBreaksUsingTheStringReplaceMethod(); + public string StringReplace() => + ReplaceLineBreak.ReplaceLineBreaksUsingTheStringReplaceMethod(_text); [Benchmark] - public string StringReplaceLineEndings() => ReplaceLineBreak.ReplaceLineBreaksUsingTheStringReplaceLineEndingsMethod(); + public string StringReplaceLineEndings() => + ReplaceLineBreak.ReplaceLineBreaksUsingTheStringReplaceLineEndingsMethod(_text); [Benchmark] - public string RegexReplace() => ReplaceLineBreak.ReplaceLineBreaksUsingTheRegularExpressionReplaceMethod(); + public string RegexReplace() => + ReplaceLineBreak.ReplaceLineBreaksUsingTheRegularExpressionReplaceMethod(_text); } diff --git a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpBenchmarks/ReplaceLineBreaksInAStringCSharpBenchmarks.csproj b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpBenchmarks/ReplaceLineBreaksInAStringCSharpBenchmarks.csproj index a07458b65d..36d2d1dd3a 100644 --- a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpBenchmarks/ReplaceLineBreaksInAStringCSharpBenchmarks.csproj +++ b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpBenchmarks/ReplaceLineBreaksInAStringCSharpBenchmarks.csproj @@ -2,13 +2,13 @@ Exe - net7.0 + net10.0 enable enable - + diff --git a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpTests/ReplaceLineBreakTests.cs b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpTests/ReplaceLineBreakTests.cs index 4cf6075d3f..11173702c5 100644 --- a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpTests/ReplaceLineBreakTests.cs +++ b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpTests/ReplaceLineBreakTests.cs @@ -4,10 +4,10 @@ namespace ReplaceLineBreaksInAStringCSharpTests; public class ReplaceLineBreakTests { - private const string Expected = "This is a line.\nThis is another line."; - + private const string Expected = "Line one.\nLine two.\nLine three.\nLine four."; + [Fact] - public void WhenReplaceLineBreaksUsingTheStringReplaceMethod_ThenReturnStringOnOneLine() + public void WhenReplaceLineBreaksUsingTheStringReplaceMethod_ThenReturnStringWithUpdatedLineEndings() { var actual = ReplaceLineBreak.ReplaceLineBreaksUsingTheStringReplaceMethod(); @@ -15,7 +15,7 @@ public void WhenReplaceLineBreaksUsingTheStringReplaceMethod_ThenReturnStringOnO } [Fact] - public void WhenReplaceLineBreaksUsingTheStringReplaceLineEndingsMethod_ThenReturnStringWithUpdatedLineEnding() + public void WhenReplaceLineBreaksUsingTheStringReplaceLineEndingsMethod_ThenReturnStringWithUpdatedLineEndings() { var actual = ReplaceLineBreak.ReplaceLineBreaksUsingTheStringReplaceLineEndingsMethod(); @@ -23,10 +23,42 @@ public void WhenReplaceLineBreaksUsingTheStringReplaceLineEndingsMethod_ThenRetu } [Fact] - public void WhenReplaceLineBreaksUsingTheRegularExpressionReplaceMethod_ThenReturnStringWithUpdatedLineEnding() + public void WhenReplaceLineBreaksUsingTheRegularExpressionReplaceMethod_ThenReturnStringWithUpdatedLineEndings() { var actual = ReplaceLineBreak.ReplaceLineBreaksUsingTheRegularExpressionReplaceMethod(); Assert.Equal(Expected, actual); } + + [Fact] + public void WhenRemoveLineBreaks_ThenReturnStringWithNoLineBreaksAtAll() + { + var actual = ReplaceLineBreak.RemoveLineBreaks(); + + Assert.Equal("Line one.Line two.Line three.Line four.", actual); + } + + [Fact] + public void WhenReplacingCarriageReturnBeforeCarriageReturnLineFeed_ThenWindowsLineBreakDoubles() + { + const string text = "Line one.\r\nLine two."; + + var wrongOrder = text.Replace("\r", "\n"); + var rightOrder = text.Replace("\r\n", "\n").Replace("\r", "\n"); + + Assert.Equal("Line one.\n\nLine two.", wrongOrder); + Assert.Equal("Line one.\nLine two.", rightOrder); + } + + [Fact] + public void WhenTextContainsALineSeparator_ThenOnlyReplaceLineEndingsMatchesIt() + { + const string text = "Line one.\u2028Line two."; + + var replaceLineEndings = ReplaceLineBreak.ReplaceLineBreaksUsingTheStringReplaceLineEndingsMethod(text); + var replaceChain = ReplaceLineBreak.ReplaceLineBreaksUsingTheStringReplaceMethod(text); + + Assert.Equal("Line one.\nLine two.", replaceLineEndings); + Assert.Equal(text, replaceChain); + } } diff --git a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpTests/ReplaceLineBreaksInAStringCSharpTests.csproj b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpTests/ReplaceLineBreaksInAStringCSharpTests.csproj index a55eb49d87..6be2d14a68 100644 --- a/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpTests/ReplaceLineBreaksInAStringCSharpTests.csproj +++ b/strings-csharp/ReplaceLineBreaksInAStringCSharp/ReplaceLineBreaksInAStringCSharpTests/ReplaceLineBreaksInAStringCSharpTests.csproj @@ -1,7 +1,7 @@ - net7.0 + net10.0 enable enable @@ -10,13 +10,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all