diff --git a/strings-csharp/FirstLetterToUpper/FirstLetterToUpper/FirstLetterToUpper.csproj b/strings-csharp/FirstLetterToUpper/FirstLetterToUpper/FirstLetterToUpper.csproj index 05f194ae53..315bf66695 100644 --- a/strings-csharp/FirstLetterToUpper/FirstLetterToUpper/FirstLetterToUpper.csproj +++ b/strings-csharp/FirstLetterToUpper/FirstLetterToUpper/FirstLetterToUpper.csproj @@ -2,15 +2,15 @@ Exe - net6.0 + net10.0 enable enable true - - + + diff --git a/strings-csharp/FirstLetterToUpper/FirstLetterToUpper/FirstLetterToUpperMethods.cs b/strings-csharp/FirstLetterToUpper/FirstLetterToUpper/FirstLetterToUpperMethods.cs index 0e5c4424b1..395647d7e6 100644 --- a/strings-csharp/FirstLetterToUpper/FirstLetterToUpper/FirstLetterToUpperMethods.cs +++ b/strings-csharp/FirstLetterToUpper/FirstLetterToUpper/FirstLetterToUpperMethods.cs @@ -1,145 +1,156 @@ -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using System.Text.RegularExpressions; -namespace FirstLetterToUpper +namespace FirstLetterToUpper; + +[MemoryDiagnoser] +[Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] +[RankColumn] +public partial class FirstLetterToUpperMethods { - [MemoryDiagnoser] - [Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] - [RankColumn] - public class FirstLetterToUpperMethods + public IEnumerable SampleStrings() + { + yield return new object[] { GenerateRandomString(2000)}; + } + + [Benchmark] + [ArgumentsSource(nameof(SampleStrings))] + public string FirstCharSubstring(string input) { - public IEnumerable SampleStrings() + if (string.IsNullOrEmpty(input)) { - yield return new object[] { GenerateRandomString(2000)}; + return string.Empty; } - [Benchmark] - [ArgumentsSource(nameof(SampleStrings))] - public string FirstCharSubstring(string input) - { - if (string.IsNullOrEmpty(input)) - { - return string.Empty; - } + return $"{input[0].ToString().ToUpper()}{input.Substring(1)}"; + } - return $"{input[0].ToString().ToUpper()}{input.Substring(1)}"; + [Benchmark] + [ArgumentsSource(nameof(SampleStrings))] + public string FirstCharToUpper(string input) + { + if (string.IsNullOrEmpty(input)) + { + return string.Empty; } - [Benchmark] - [ArgumentsSource(nameof(SampleStrings))] - public string FirstCharToUpper(string input) - { - if (string.IsNullOrEmpty(input)) - { - return string.Empty; - } + return $"{char.ToUpper(input[0])}{input[1..]}"; + } - return $"{char.ToUpper(input[0])}{input[1..]}"; + [Benchmark] + [ArgumentsSource(nameof(SampleStrings))] + public string FirstCharToCharArray(string input) + { + if (string.IsNullOrEmpty(input)) + { + return string.Empty; } - [Benchmark] - [ArgumentsSource(nameof(SampleStrings))] - public string FirstCharToCharArray(string input) - { - if (string.IsNullOrEmpty(input)) - { - return string.Empty; - } + var stringArray = input.ToCharArray(); - var stringArray = input.ToCharArray(); + if (char.IsLower(stringArray[0])) + { + stringArray[0] = char.ToUpper(stringArray[0]); + } - if (char.IsLower(stringArray[0])) - { - stringArray[0] = char.ToUpper(stringArray[0]); - } + return new string(stringArray); + } - return new string(stringArray); + [Benchmark] + [ArgumentsSource(nameof(SampleStrings))] + public string FirstCharToUpperAsSpan(string input) + { + if (string.IsNullOrEmpty(input)) + { + return string.Empty; } - [Benchmark] - [ArgumentsSource(nameof(SampleStrings))] - public string FirstCharToUpperAsSpan(string input) - { - if (string.IsNullOrEmpty(input)) - { - return string.Empty; - } + Span destination = stackalloc char[1]; - Span destination = stackalloc char[1]; + input.AsSpan(0, 1).ToUpperInvariant(destination); - input.AsSpan(0, 1).ToUpperInvariant(destination); + return $"{destination}{input.AsSpan(1)}"; + } - return $"{destination}{input.AsSpan(1)}"; - } + [GeneratedRegex("^\\p{Ll}")] + private static partial Regex FirstLowerLetter(); - [Benchmark] - [ArgumentsSource(nameof(SampleStrings))] - public string FirstCharToUpperRegex(string input) + [Benchmark] + [ArgumentsSource(nameof(SampleStrings))] + public string FirstCharToUpperRegex(string input) + { + if (string.IsNullOrEmpty(input)) { - if (string.IsNullOrEmpty(input)) - { - return string.Empty; - } - - return Regex.Replace(input, "^[a-z]", c => c.Value.ToUpper()); + return string.Empty; } - [Benchmark] - [ArgumentsSource(nameof(SampleStrings))] - public string FirstCharToUpperLinq(string input) - { - if (string.IsNullOrEmpty(input)) - { - return string.Empty; - } + return FirstLowerLetter().Replace(input, c => c.Value.ToUpper()); + } - return $"{input.FirstOrDefault().ToString().ToUpper()}{input.Substring(1)}"; + [Benchmark] + [ArgumentsSource(nameof(SampleStrings))] + public string FirstCharToUpperLinq(string input) + { + if (string.IsNullOrEmpty(input)) + { + return string.Empty; } + return $"{input.FirstOrDefault().ToString().ToUpper()}{input.Substring(1)}"; + } - [Benchmark] - [ArgumentsSource(nameof(SampleStrings))] - public string FirstCharToUpperStringCreate(string input) - { - if (string.IsNullOrEmpty(input)) - { - return string.Empty; - } - return string.Create(input.Length, input, static (Span chars, string str) => - { - chars[0] = char.ToUpperInvariant(str[0]); - str.AsSpan(1).CopyTo(chars[1..]); - }); + [Benchmark] + [ArgumentsSource(nameof(SampleStrings))] + public string FirstCharToUpperStringCreate(string input) + { + if (string.IsNullOrEmpty(input)) + { + return string.Empty; } - [Benchmark] - [ArgumentsSource(nameof(SampleStrings))] - public string FirstCharToUpperUnsafeCode(string input) + return string.Create(input.Length, input, static (Span chars, string str) => { - if (string.IsNullOrEmpty(input)) - { - return string.Empty; - } + chars[0] = char.ToUpperInvariant(str[0]); + str.AsSpan(1).CopyTo(chars[1..]); + }); + } + + /// + /// Demonstration code only. This method does not build a new string: it pins the string it was + /// given and overwrites its first character in place, so every other variable, field or + /// dictionary key pointing at the same instance changes with it. Identical string literals in an + /// assembly share one interned instance, which makes the damage reach code this method never saw. + /// Do not use it in production. + /// + [Benchmark] + [ArgumentsSource(nameof(SampleStrings))] + public string FirstCharToUpperUnsafeCode(string input) + { + if (string.IsNullOrEmpty(input)) + { + return string.Empty; + } - unsafe + unsafe + { + // Mutates the instance itself. If input is an interned literal, every reference to + // that literal now reads the modified value. See the XML comment above. + fixed (char* p = input) { - fixed (char* p = input) - { - *p = char.ToUpper(*p); - } + *p = char.ToUpper(*p); } - - return input; } - private string GenerateRandomString(int size) - { - var random = new Random(); + return input; + } - var charOptions = "abcdefghijklmnopqrstuvwxyz"; + private string GenerateRandomString(int size) + { + var random = new Random(); - return new string(Enumerable.Repeat(charOptions, size).Select(s => s[random.Next(s.Length)]).ToArray()).ToLower(); - } + var charOptions = "abcdefghijklmnopqrstuvwxyz"; + + return new string(Enumerable.Repeat(charOptions, size).Select(s => s[random.Next(s.Length)]).ToArray()).ToLower(); } } diff --git a/strings-csharp/FirstLetterToUpper/FirstLetterToUpperTests/FirstLetterToUpperTests.csproj b/strings-csharp/FirstLetterToUpper/FirstLetterToUpperTests/FirstLetterToUpperTests.csproj index 8223e9ede4..564c26c729 100644 --- a/strings-csharp/FirstLetterToUpper/FirstLetterToUpperTests/FirstLetterToUpperTests.csproj +++ b/strings-csharp/FirstLetterToUpper/FirstLetterToUpperTests/FirstLetterToUpperTests.csproj @@ -1,7 +1,7 @@ - net6.0 + net10.0 enable enable @@ -9,10 +9,10 @@ - - - - + + + + diff --git a/strings-csharp/FirstLetterToUpper/FirstLetterToUpperTests/FirstLetterToUpperUnitTests.cs b/strings-csharp/FirstLetterToUpper/FirstLetterToUpperTests/FirstLetterToUpperUnitTests.cs index 2140bcbd71..bf6c764475 100644 --- a/strings-csharp/FirstLetterToUpper/FirstLetterToUpperTests/FirstLetterToUpperUnitTests.cs +++ b/strings-csharp/FirstLetterToUpper/FirstLetterToUpperTests/FirstLetterToUpperUnitTests.cs @@ -1,82 +1,101 @@ using FirstLetterToUpper; -namespace FirstLetterToUpperTests +namespace FirstLetterToUpperTests; + +[TestClass] +public class FirstLetterToUpperUnitTests { - [TestClass] - public class FirstLetterToUpperUnitTests + private readonly FirstLetterToUpperMethods _upperCase = new(); + + [TestMethod] + public void GivenALowerCaseString_WhenUsingSubStringTechnique_VerifyFirstCharIsUpper() + { + var testString = "this is a test string"; + var returnedString = _upperCase.FirstCharSubstring(testString); + + Assert.AreEqual("This is a test string", returnedString); + } + + [TestMethod] + public void GivenALowerCaseString_WhenUsingCharToUpperTechnique_VerifyFirstCharIsUpper() + { + var testString = "this is a test string"; + var returnedString = _upperCase.FirstCharToUpper(testString); + + Assert.AreEqual("This is a test string", returnedString); + } + + [TestMethod] + public void GivenALowerCaseString_WhenUsingCharArrayTechnique_VerifyFirstCharIsUpper() + { + var testString = "this is a test string"; + var returnedString = _upperCase.FirstCharToCharArray(testString); + + Assert.AreEqual("This is a test string", returnedString); + } + + [TestMethod] + public void GivenALowerCaseString_WhenUsingAsSpanTechnique_VerifyFirstCharIsUpper() + { + var testString = "this is a test string"; + var returnedString = _upperCase.FirstCharToUpperAsSpan(testString); + + Assert.AreEqual("This is a test string", returnedString); + } + + [TestMethod] + public void GivenALowerCaseString_WhenUsingStringCreateTechnique_VerifyFirstCharIsUpper() + { + var testString = "this is a test string"; + var returnedString = _upperCase.FirstCharToUpperStringCreate(testString); + + Assert.AreEqual("This is a test string", returnedString); + } + + [TestMethod] + public void GivenALowerCaseString_WhenUsingRegexTechnique_VerifyFirstCharIsUpper() + { + var testString = "this is a test string"; + var returnedString = _upperCase.FirstCharToUpperRegex(testString); + + Assert.AreEqual("This is a test string", returnedString); + } + + [TestMethod] + public void GivenANonAsciiLowerCaseString_WhenUsingRegexTechnique_VerifyFirstCharIsUpper() + { + var testString = "élan vital"; + var returnedString = _upperCase.FirstCharToUpperRegex(testString); + + Assert.AreEqual("Élan vital", returnedString); + } + + [TestMethod] + public void GivenALowerCaseString_WhenUsingLinqTechnique_VerifyFirstCharIsUpper() { - FirstLetterToUpperMethods upperCase = new FirstLetterToUpperMethods(); - - [TestMethod] - public void GivenALowerCaseString_WhenUsingSubStringTechnique_VerifyFirstCharIsUpper() - { - var testString = "this is a test string"; - var returnedString = upperCase.FirstCharSubstring(testString); - - Assert.IsTrue(char.IsUpper(returnedString[0])); - } - - [TestMethod] - public void GivenALowerCaseString_WhenUsingCharToUpperTechnique_VerifyFirstCharIsUpper() - { - var testString = "this is a test string"; - var returnedString = upperCase.FirstCharToUpper(testString); - - Assert.IsTrue(char.IsUpper(returnedString[0])); - } - - [TestMethod] - public void GivenALowerCaseString_WhenUsingCharArrayTechnique_VerifyFirstCharIsUpper() - { - var testString = "this is a test string"; - var returnedString = upperCase.FirstCharToCharArray(testString); - - Assert.IsTrue(char.IsUpper(returnedString[0])); - } - - [TestMethod] - public void GivenALowerCaseString_WhenUsingAsSpanTechnique_VerifyFirstCharIsUpper() - { - var testString = "this is a test string"; - var returnedString = upperCase.FirstCharToUpperAsSpan(testString); - - Assert.IsTrue(char.IsUpper(returnedString[0])); - } - - [TestMethod] - public void GivenALowerCaseString_WhenUsingStringCreateTechnique_VerifyFirstCharIsUpper() - { - var testString = "this is a test string"; - var returnedString = upperCase.FirstCharToUpperStringCreate(testString); - - Assert.IsTrue(char.IsUpper(returnedString[0])); - } - - [TestMethod] - public void GivenALowerCaseString_WhenUsingRegexTechnique_VerifyFirstCharIsUpper() - { - var testString = "this is a test string"; - var returnedString = upperCase.FirstCharToUpperRegex(testString); - - Assert.IsTrue(char.IsUpper(returnedString[0])); - } - - [TestMethod] - public void GivenALowerCaseString_WhenUsingLinqTechnique_VerifyFirstCharIsUpper() - { - var testString = "this is a test string"; - var returnedString = upperCase.FirstCharToUpperLinq(testString); - - Assert.IsTrue(char.IsUpper(returnedString[0])); - } - - [TestMethod] - public void GivenALowerCaseString_WhenUsingUnsafeCodeTechnique_VerifyFirstCharIsUpper() - { - var testString = "this is a test string"; - var returnedString = upperCase.FirstCharToUpperUnsafeCode(testString); - - Assert.IsTrue(char.IsUpper(returnedString[0])); - } + var testString = "this is a test string"; + var returnedString = _upperCase.FirstCharToUpperLinq(testString); + + Assert.AreEqual("This is a test string", returnedString); + } + + [TestMethod] + public void GivenALowerCaseString_WhenUsingUnsafeCodeTechnique_VerifyFirstCharIsUpper() + { + var testString = new string("unsafe demo input".ToCharArray()); + var returnedString = _upperCase.FirstCharToUpperUnsafeCode(testString); + + Assert.AreEqual("Unsafe demo input", returnedString); + } + + [TestMethod] + public void GivenTwoVariablesHoldingTheSameString_WhenUsingUnsafeCode_ThenBothStringsChange() + { + var first = new string("mutation demo".ToCharArray()); + var second = first; + + _upperCase.FirstCharToUpperUnsafeCode(first); + + Assert.AreEqual("Mutation demo", second); } -} \ No newline at end of file +}