Починаючи з .NET Core 2.1 існує новий спосіб повернути рядок за допомогою string.Create
методу.
Зауважте, що це рішення не обробляє Unicode об'єднання символів тощо правильно, оскільки "Les Mise \ u0301rables" буде перетворено на "selbarésiM seL". В інші відповіді для кращого вирішення.
public static string Reverse(string input)
{
return string.Create<string>(input.Length, input, (chars, state) =>
{
state.AsSpan().CopyTo(chars);
chars.Reverse();
});
}
Це по суті копіює символи input
нового рядка і повертає нову рядок на місце.
Чому string.Create
корисний?
Коли ми створюємо рядок із існуючого масиву, виділяється новий внутрішній масив і копіюються значення. В іншому випадку можна було б мутувати рядок після його створення (у безпечному середовищі). Тобто, у наступному фрагменті ми повинні виділити масив довжиною 10 вдвічі, один як буфер, а один як внутрішній масив рядка.
var chars = new char[10];
// set array values
var str = new string(chars);
string.Create
по суті дозволяє нам маніпулювати внутрішнім масивом під час створення рядка. Це означає, що нам більше не потрібен буфер, і тому ми можемо уникати виділення цього масиву char.
Стів Гордон детальніше написав про це тут . Також є стаття про MSDN .
Як користуватися string.Create
?
public static string Create<TState>(int length, TState state, SpanAction<char, TState> action);
Метод приймає три параметри:
- Довжина рядка для створення,
- дані, які ви хочете використовувати для динамічного створення нового рядка,
- і делегат, який створює заключну рядок з даних, де перший параметр вказує на внутрішній
char
масив нової рядки, а другий - дані (стан), до яких ви передали string.Create
.
Всередині делегата ми можемо вказати, як створюється нова рядок із даних. У нашому випадку ми просто копіюємо символи вхідного рядка у Span
використаний новий рядок. Потім перевертаємоSpan
і, отже, весь рядок .
Орієнтири
Для порівняння запропонованого способу перевороту рядка з прийнятою відповіддю я написав два орієнтири за допомогою BenchmarkDotNet.
public class StringExtensions
{
public static string ReverseWithArray(string input)
{
var charArray = input.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
public static string ReverseWithStringCreate(string input)
{
return string.Create(input.Length, input, (chars, state) =>
{
state.AsSpan().CopyTo(chars);
chars.Reverse();
});
}
}
[MemoryDiagnoser]
public class StringReverseBenchmarks
{
private string input;
[Params(10, 100, 1000)]
public int InputLength { get; set; }
[GlobalSetup]
public void SetInput()
{
// Creates a random string of the given length
this.input = RandomStringGenerator.GetString(InputLength);
}
[Benchmark(Baseline = true)]
public string WithReverseArray() => StringExtensions.ReverseWithArray(input);
[Benchmark]
public string WithStringCreate() => StringExtensions.ReverseWithStringCreate(input);
}
Ось результати на моїй машині:
| Method | InputLength | Mean | Error | StdDev | Gen 0 | Allocated |
| ---------------- | ----------- | -----------: | ---------: | --------: | -----: | --------: |
| WithReverseArray | 10 | 45.464 ns | 0.4836 ns | 0.4524 ns | 0.0610 | 96 B |
| WithStringCreate | 10 | 39.749 ns | 0.3206 ns | 0.2842 ns | 0.0305 | 48 B |
| | | | | | | |
| WithReverseArray | 100 | 175.162 ns | 2.8766 ns | 2.2458 ns | 0.2897 | 456 B |
| WithStringCreate | 100 | 125.284 ns | 2.4657 ns | 2.0590 ns | 0.1473 | 232 B |
| | | | | | | |
| WithReverseArray | 1000 | 1,523.544 ns | 9.8808 ns | 8.7591 ns | 2.5768 | 4056 B |
| WithStringCreate | 1000 | 1,078.957 ns | 10.2948 ns | 9.6298 ns | 1.2894 | 2032 B |
Як бачите, ReverseWithStringCreate
ми виділяємо лише половину пам'яті, використовуваної ReverseWithArray
методом.