Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using BenchmarkDotNet.Running;
using StringTruncationInCSharp;

BenchmarkRunner.Run<StringHelperBenchmark>();
BenchmarkRunner.Run<StringHelperBenchmark>(args: args);
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;

namespace StringTruncationInCSharp;
Expand Down Expand Up @@ -48,7 +49,7 @@ public static string TruncateWithRegularExpressions(string originalString, int m
return TruncateString(originalString, maxLength, UsingRegularExpressions);

static string UsingRegularExpressions(string str, int length) =>
Regex.Replace(str, $"^(.{{0,{length}}}).*$", "$1");
Regex.Replace(str, $"^(.{{0,{length}}}).*$", "$1", RegexOptions.Singleline);
}

public static string TruncateWithSpan(string originalString, int maxLength)
Expand All @@ -58,6 +59,38 @@ public static string TruncateWithSpan(string originalString, int maxLength)
static string UsingSpan(string str, int length) => str.AsSpan()[..length].ToString();
}

public static string TruncateWithSurrogateCheck(string originalString, int maxLength)
{
return TruncateString(originalString, maxLength, UsingSurrogateCheck);

static string UsingSurrogateCheck(string str, int length) =>
char.IsHighSurrogate(str[length - 1]) ? str[..(length - 1)] : str[..length];
}

public static string TruncateWithTextElements(string originalString, int maxLength)
{
return TruncateString(originalString, maxLength, UsingTextElements);

static string UsingTextElements(string str, int length)
{
var end = 0;

while (end < length)
{
var next = StringInfo.GetNextTextElementLength(str, end);

if (end + next > length)
{
break;
}

end += next;
}

return str[..end];
}
}

private static string TruncateString(string originalString, int maxLength,
Func<string, int, string> specificTruncateMethod)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,44 @@ public class StringHelperBenchmark
public int MaxLength;

[Benchmark]
public void TruncateWithSubstring()
public string TruncateWithSubstring()
{
StringHelper.TruncateWithSubstring(_originalString, MaxLength);
return StringHelper.TruncateWithSubstring(_originalString, MaxLength);
}

[Benchmark]
public void TruncateWithForLoopStringBuilder()
public string TruncateWithForLoopStringBuilder()
{
StringHelper.TruncateWithForLoopStringBuilder(_originalString, MaxLength);
return StringHelper.TruncateWithForLoopStringBuilder(_originalString, MaxLength);
}

[Benchmark]
public void TruncateWithRegularExpressions()
public string TruncateWithRegularExpressions()
{
StringHelper.TruncateWithRegularExpressions(_originalString, MaxLength);
return StringHelper.TruncateWithRegularExpressions(_originalString, MaxLength);
}

[Benchmark]
public void TruncateWithRemove()
public string TruncateWithRemove()
{
StringHelper.TruncateWithRemove(_originalString, MaxLength);
return StringHelper.TruncateWithRemove(_originalString, MaxLength);
}

[Benchmark]
public void TruncateWithLINQ()
public string TruncateWithLINQ()
{
StringHelper.TruncateWithLINQ(_originalString, MaxLength);
return StringHelper.TruncateWithLINQ(_originalString, MaxLength);
}

[Benchmark]
public void TruncateWithSpan()
public string TruncateWithSpan()
{
StringHelper.TruncateWithSpan(_originalString, MaxLength);
return StringHelper.TruncateWithSpan(_originalString, MaxLength);
}

[Benchmark]
public string TruncateWithTextElements()
{
return StringHelper.TruncateWithTextElements(_originalString, MaxLength);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.9" />
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

Expand All @@ -10,13 +10,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PackageReference Include="coverlet.collector" Version="10.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,85 @@ public void GivenANonPositiveLength_WhenTruncatingAString_ThenUseSpanMethodAndRe

Assert.Equal(string.Empty, truncatedString);
}

[Fact]
public void GivenAMultiLineString_WhenTruncatingAString_ThenUseRegularExpressionsMethodToTruncateTheString()
{
var truncatedString = StringHelper.TruncateWithRegularExpressions("First line\nSecond line", 5);

Assert.Equal("First", truncatedString);
}

[Fact]
public void GivenATrailingNewLine_WhenTruncatingAString_ThenUseRegularExpressionsMethodToTruncateTheString()
{
var truncatedString = StringHelper.TruncateWithRegularExpressions("Hello world\n", 5);

Assert.Equal("Hello", truncatedString);
}


[Fact]
public void WhenTruncatingAString_ThenUseTextElementsMethodToTruncateTheString()
{
var truncatedString = StringHelper.TruncateWithTextElements(OriginalString, MaxLength);

Assert.Equal(ExpectedTruncatedString, truncatedString);
}

[Fact]
public void GivenAZeroLength_WhenTruncatingAString_ThenUseTextElementsMethodAndReturnAnEmptyString()
{
var truncatedString = StringHelper.TruncateWithTextElements(OriginalString, 0);

Assert.Equal(string.Empty, truncatedString);
}

[Fact]
public void GivenALengthGreaterThanTheStringLength_WhenTruncatingAString_ThenUseTextElementsMethodWithoutTruncatingTheString()
{
var truncatedString = StringHelper.TruncateWithTextElements(OriginalString, OriginalString.Length + 10);

Assert.Equal(OriginalString, truncatedString);
}

[Fact]
public void GivenAnEmojiAtTheCut_WhenTruncatingWithSubstring_ThenTheResultEndsWithAHalfSurrogatePair()
{
var truncatedString = StringHelper.TruncateWithSubstring("Thumbs up \U0001F44D", 11);

Assert.True(char.IsHighSurrogate(truncatedString[^1]));
}

[Fact]
public void GivenAnEmojiAtTheCut_WhenTruncatingWithTextElements_ThenTheWholeEmojiIsDropped()
{
var truncatedString = StringHelper.TruncateWithTextElements("Thumbs up \U0001F44D", 11);

Assert.Equal("Thumbs up ", truncatedString);
}

[Fact]
public void GivenAJoinedEmojiAtTheCut_WhenTruncatingWithTextElements_ThenTheWholeEmojiIsDropped()
{
var truncatedString = StringHelper.TruncateWithTextElements("Family \U0001F468\u200D\U0001F469\u200D\U0001F467", 12);

Assert.Equal("Family ", truncatedString);
}

[Fact]
public void GivenAnEmojiAtTheCut_WhenTruncatingWithSurrogateCheck_ThenTheWholeEmojiIsDropped()
{
var truncatedString = StringHelper.TruncateWithSurrogateCheck("Thumbs up \U0001F44D", 11);

Assert.Equal("Thumbs up ", truncatedString);
}

[Fact]
public void WhenTruncatingAString_ThenUseSurrogateCheckMethodToTruncateTheString()
{
var truncatedString = StringHelper.TruncateWithSurrogateCheck(OriginalString, MaxLength);

Assert.Equal(ExpectedTruncatedString, truncatedString);
}
}
Loading