连接 ReadOnlySpan<char>
Concatenate ReadOnlySpan<char>
好的,.NET Core 2.1 已经落地。有了它,我们获得了一种处理字符串数据 ReadOnlySpan<char>
的新方法。它非常适合拆分字符串数据,但是如何将跨度重新组合在一起呢?
var hello = "Hello".AsSpan();
var space = " ".AsSpan();
var world = "World".AsSpan();
var result = ...; // How do I get "Hello World" out of the 3 above?
下面是 .NET 团队如何在内部为 Path.Join 处理此问题的示例:
private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second)
{
Debug.Assert(first.Length > 0 && second.Length > 0, "should have dealt with empty paths");
bool hasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1])
|| PathInternal.IsDirectorySeparator(second[0]);
fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second))
{
return string.Create(
first.Length + second.Length + (hasSeparator ? 0 : 1),
(First: (IntPtr)f, FirstLength: first.Length, Second: (IntPtr)s, SecondLength: second.Length, HasSeparator: hasSeparator),
(destination, state) =>
{
new Span<char>((char*)state.First, state.FirstLength).CopyTo(destination);
if (!state.HasSeparator)
destination[state.FirstLength] = PathInternal.DirectorySeparatorChar;
new Span<char>((char*)state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.HasSeparator ? 0 : 1)));
});
}
}
如果您想避免使用 unsafe
并使用 可能 更易于阅读的内容,您可以使用类似的内容:
public static ReadOnlySpan<char> Concat(this ReadOnlySpan<char> first, ReadOnlySpan<char> second)
{
return new string(first.ToArray().Concat(second.ToArray()).ToArray()).AsSpan();
}
public static ReadOnlySpan<char> Concat(this string first, ReadOnlySpan<char> second)
{
return new string(first.ToArray().Concat(second.ToArray()).ToArray()).ToArray();
}
使用 ReadOnlySpan
是相当低的级别并且针对速度进行了优化,因此您如何使用可能取决于您自己的情况。但在许多情况下,回到 string
插值和 StringBuilder
可能没问题(或者根本不转换为 ReadOnlySpan
)。所以
var sb = new StringBuilder();
return sb
.Append(hello)
.Append(space)
.Append(world)
.ToString();
或
return $"{hello.ToString()}{space.ToString()}{world.ToString()}";
您可以使用这样的缓冲区来实现这一点=>
var hello = "Hello".AsSpan();
var space = " ".AsSpan();
var world = "World".AsSpan();
// First allocate the buffer with the target size
char[] buffer = new char[hello.Length + space.Length + world.Length];
// "Convert" it to writable Span<char>
var span = new Span<char>(buffer);
// Then copy each span at the right position in the buffer
int index = 0;
hello.CopyTo(span.Slice(index, hello.Length));
index += hello.Length;
space.CopyTo(span.Slice(index, space.Length));
index += space.Length;
world.CopyTo(span.Slice(index, world.Length));
// Finality get back the string
string result = span.ToString();
您可以通过使用数组池重用缓冲区来再次优化
char[] buffer = ArrayPool<char>.Shared.Rent(hello.Length + space.Length + world.Length);
// ...
ArrayPool<char>.Shared.Return(buffer);
我周五下午的快速想法:
var hello = "Hello";
var helloS = hello.AsSpan();
var spaceS = " ".AsSpan();
var worldS = "World".AsSpan();
var sentence = helloS.ToString() + spaceS.ToString() + worldS.ToString();
//Gives "Hello World"
至少根据我在 LinqPad 上的快速播放,以及对 System.Memory Source
的快速阅读
另一种选择是使用 string.Concat,它接受 ReadOnlySpan 作为参数。这是取自 github
的实现
internal static unsafe string Concat(ReadOnlySpan<char> str0, ReadOnlySpan<char> str1, ReadOnlySpan<char> str2)
{
var result = new string('[=10=]', checked(str0.Length + str1.Length + str2.Length));
fixed (char* resultPtr = result)
{
var resultSpan = new Span<char>(resultPtr, result.Length);
str0.CopyTo(resultSpan);
resultSpan = resultSpan.Slice(str0.Length);
str1.CopyTo(resultSpan);
resultSpan = resultSpan.Slice(str1.Length);
str2.CopyTo(resultSpan);
}
return result;
}
我编写并使用了以下 extension/tool 方法来连接跨度:
ReadOnlySpan<T> otherSpan = ...
T[] someArray = ...
var myArray = someSpan.Concat(otherSpan, someArray, etc);
var myArray2 = SpanTool.Concat(someArray, otherSpan, etc);
SpanExtensions.cs
public static class SpanExtensions {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] Concat<T>(this ReadOnlySpan<T> span0, ReadOnlySpan<T> span1)
=> SpanTool.Concat(span0, span1);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] Concat<T>(this ReadOnlySpan<T> span0, ReadOnlySpan<T> span1, ReadOnlySpan<T> span2)
=> SpanTool.Concat(span0, span1, span2);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] Concat<T>(this ReadOnlySpan<T> span0, ReadOnlySpan<T> span1, ReadOnlySpan<T> span2, ReadOnlySpan<T> span3)
=> SpanTool.Concat(span0, span1, span2, span3);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] Concat<T>(this ReadOnlySpan<T> span0, ReadOnlySpan<T> span1, ReadOnlySpan<T> span2, ReadOnlySpan<T> span3, ReadOnlySpan<T> span4)
=> SpanTool.Concat(span0, span1, span2, span3, span4);
}
SpanTool.cs
public static class SpanTool {
public static T[] Concat<T>(ReadOnlySpan<T> span0) {
var result = new T[span0.Length];
span0.CopyTo(result);
return result;
}
public static T[] Concat<T>(ReadOnlySpan<T> span0, ReadOnlySpan<T> span1) {
var result = new T[span0.Length + span1.Length];
var resultSpan = result.AsSpan();
span0.CopyTo(result);
var from = span0.Length;
span1.CopyTo(resultSpan.Slice(from));
return result;
}
public static T[] Concat<T>(ReadOnlySpan<T> span0, ReadOnlySpan<T> span1, ReadOnlySpan<T> span2) {
var result = new T[span0.Length + span1.Length + span2.Length];
var resultSpan = result.AsSpan();
span0.CopyTo(result);
var from = span0.Length;
span1.CopyTo(resultSpan.Slice(from));
from += span1.Length;
span2.CopyTo(resultSpan.Slice(from));
return result;
}
public static T[] Concat<T>(ReadOnlySpan<T> span0, ReadOnlySpan<T> span1, ReadOnlySpan<T> span2, ReadOnlySpan<T> span3) {
var result = new T[span0.Length + span1.Length + span2.Length + span3.Length];
var resultSpan = result.AsSpan();
span0.CopyTo(result);
var from = span0.Length;
span1.CopyTo(resultSpan.Slice(from));
from += span1.Length;
span2.CopyTo(resultSpan.Slice(from));
from += span2.Length;
span3.CopyTo(resultSpan.Slice(from));
return result;
}
public static T[] Concat<T>(ReadOnlySpan<T> span0, ReadOnlySpan<T> span1, ReadOnlySpan<T> span2, ReadOnlySpan<T> span3, ReadOnlySpan<T> span4) {
var result = new T[span0.Length + span1.Length + span2.Length + span3.Length + span4.Length];
var resultSpan = result.AsSpan();
span0.CopyTo(result);
var from = span0.Length;
span1.CopyTo(resultSpan.Slice(from));
from += span1.Length;
span2.CopyTo(resultSpan.Slice(from));
from += span2.Length;
span3.CopyTo(resultSpan.Slice(from));
from += span3.Length;
span4.CopyTo(resultSpan.Slice(from));
return result;
}
}
免责声明 我刚刚为我的项目写了这篇文章,因为这里的答案并不令人满意。如有错误,稍后编辑。
我认为值得一提的是,.NET Core 3 中添加了连接跨度的重载,并且对 .NET Core 2.1 的支持已于 2021 年 8 月 21 日结束 [src]。如果你现在升级那么你可以简单地使用 String.Concat.
var hello = "Hello".AsSpan();
var space = " ".AsSpan();
var world = "World".AsSpan();
// .NET Core 3+
var result = string.Concat(hello, space, world);
好的,.NET Core 2.1 已经落地。有了它,我们获得了一种处理字符串数据 ReadOnlySpan<char>
的新方法。它非常适合拆分字符串数据,但是如何将跨度重新组合在一起呢?
var hello = "Hello".AsSpan();
var space = " ".AsSpan();
var world = "World".AsSpan();
var result = ...; // How do I get "Hello World" out of the 3 above?
下面是 .NET 团队如何在内部为 Path.Join 处理此问题的示例:
private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second)
{
Debug.Assert(first.Length > 0 && second.Length > 0, "should have dealt with empty paths");
bool hasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1])
|| PathInternal.IsDirectorySeparator(second[0]);
fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second))
{
return string.Create(
first.Length + second.Length + (hasSeparator ? 0 : 1),
(First: (IntPtr)f, FirstLength: first.Length, Second: (IntPtr)s, SecondLength: second.Length, HasSeparator: hasSeparator),
(destination, state) =>
{
new Span<char>((char*)state.First, state.FirstLength).CopyTo(destination);
if (!state.HasSeparator)
destination[state.FirstLength] = PathInternal.DirectorySeparatorChar;
new Span<char>((char*)state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.HasSeparator ? 0 : 1)));
});
}
}
如果您想避免使用 unsafe
并使用 可能 更易于阅读的内容,您可以使用类似的内容:
public static ReadOnlySpan<char> Concat(this ReadOnlySpan<char> first, ReadOnlySpan<char> second)
{
return new string(first.ToArray().Concat(second.ToArray()).ToArray()).AsSpan();
}
public static ReadOnlySpan<char> Concat(this string first, ReadOnlySpan<char> second)
{
return new string(first.ToArray().Concat(second.ToArray()).ToArray()).ToArray();
}
使用 ReadOnlySpan
是相当低的级别并且针对速度进行了优化,因此您如何使用可能取决于您自己的情况。但在许多情况下,回到 string
插值和 StringBuilder
可能没问题(或者根本不转换为 ReadOnlySpan
)。所以
var sb = new StringBuilder();
return sb
.Append(hello)
.Append(space)
.Append(world)
.ToString();
或
return $"{hello.ToString()}{space.ToString()}{world.ToString()}";
您可以使用这样的缓冲区来实现这一点=>
var hello = "Hello".AsSpan();
var space = " ".AsSpan();
var world = "World".AsSpan();
// First allocate the buffer with the target size
char[] buffer = new char[hello.Length + space.Length + world.Length];
// "Convert" it to writable Span<char>
var span = new Span<char>(buffer);
// Then copy each span at the right position in the buffer
int index = 0;
hello.CopyTo(span.Slice(index, hello.Length));
index += hello.Length;
space.CopyTo(span.Slice(index, space.Length));
index += space.Length;
world.CopyTo(span.Slice(index, world.Length));
// Finality get back the string
string result = span.ToString();
您可以通过使用数组池重用缓冲区来再次优化
char[] buffer = ArrayPool<char>.Shared.Rent(hello.Length + space.Length + world.Length);
// ...
ArrayPool<char>.Shared.Return(buffer);
我周五下午的快速想法:
var hello = "Hello";
var helloS = hello.AsSpan();
var spaceS = " ".AsSpan();
var worldS = "World".AsSpan();
var sentence = helloS.ToString() + spaceS.ToString() + worldS.ToString();
//Gives "Hello World"
至少根据我在 LinqPad 上的快速播放,以及对 System.Memory Source
的快速阅读另一种选择是使用 string.Concat,它接受 ReadOnlySpan 作为参数。这是取自 github
的实现internal static unsafe string Concat(ReadOnlySpan<char> str0, ReadOnlySpan<char> str1, ReadOnlySpan<char> str2)
{
var result = new string('[=10=]', checked(str0.Length + str1.Length + str2.Length));
fixed (char* resultPtr = result)
{
var resultSpan = new Span<char>(resultPtr, result.Length);
str0.CopyTo(resultSpan);
resultSpan = resultSpan.Slice(str0.Length);
str1.CopyTo(resultSpan);
resultSpan = resultSpan.Slice(str1.Length);
str2.CopyTo(resultSpan);
}
return result;
}
我编写并使用了以下 extension/tool 方法来连接跨度:
ReadOnlySpan<T> otherSpan = ...
T[] someArray = ...
var myArray = someSpan.Concat(otherSpan, someArray, etc);
var myArray2 = SpanTool.Concat(someArray, otherSpan, etc);
SpanExtensions.cs
public static class SpanExtensions {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] Concat<T>(this ReadOnlySpan<T> span0, ReadOnlySpan<T> span1)
=> SpanTool.Concat(span0, span1);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] Concat<T>(this ReadOnlySpan<T> span0, ReadOnlySpan<T> span1, ReadOnlySpan<T> span2)
=> SpanTool.Concat(span0, span1, span2);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] Concat<T>(this ReadOnlySpan<T> span0, ReadOnlySpan<T> span1, ReadOnlySpan<T> span2, ReadOnlySpan<T> span3)
=> SpanTool.Concat(span0, span1, span2, span3);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] Concat<T>(this ReadOnlySpan<T> span0, ReadOnlySpan<T> span1, ReadOnlySpan<T> span2, ReadOnlySpan<T> span3, ReadOnlySpan<T> span4)
=> SpanTool.Concat(span0, span1, span2, span3, span4);
}
SpanTool.cs
public static class SpanTool {
public static T[] Concat<T>(ReadOnlySpan<T> span0) {
var result = new T[span0.Length];
span0.CopyTo(result);
return result;
}
public static T[] Concat<T>(ReadOnlySpan<T> span0, ReadOnlySpan<T> span1) {
var result = new T[span0.Length + span1.Length];
var resultSpan = result.AsSpan();
span0.CopyTo(result);
var from = span0.Length;
span1.CopyTo(resultSpan.Slice(from));
return result;
}
public static T[] Concat<T>(ReadOnlySpan<T> span0, ReadOnlySpan<T> span1, ReadOnlySpan<T> span2) {
var result = new T[span0.Length + span1.Length + span2.Length];
var resultSpan = result.AsSpan();
span0.CopyTo(result);
var from = span0.Length;
span1.CopyTo(resultSpan.Slice(from));
from += span1.Length;
span2.CopyTo(resultSpan.Slice(from));
return result;
}
public static T[] Concat<T>(ReadOnlySpan<T> span0, ReadOnlySpan<T> span1, ReadOnlySpan<T> span2, ReadOnlySpan<T> span3) {
var result = new T[span0.Length + span1.Length + span2.Length + span3.Length];
var resultSpan = result.AsSpan();
span0.CopyTo(result);
var from = span0.Length;
span1.CopyTo(resultSpan.Slice(from));
from += span1.Length;
span2.CopyTo(resultSpan.Slice(from));
from += span2.Length;
span3.CopyTo(resultSpan.Slice(from));
return result;
}
public static T[] Concat<T>(ReadOnlySpan<T> span0, ReadOnlySpan<T> span1, ReadOnlySpan<T> span2, ReadOnlySpan<T> span3, ReadOnlySpan<T> span4) {
var result = new T[span0.Length + span1.Length + span2.Length + span3.Length + span4.Length];
var resultSpan = result.AsSpan();
span0.CopyTo(result);
var from = span0.Length;
span1.CopyTo(resultSpan.Slice(from));
from += span1.Length;
span2.CopyTo(resultSpan.Slice(from));
from += span2.Length;
span3.CopyTo(resultSpan.Slice(from));
from += span3.Length;
span4.CopyTo(resultSpan.Slice(from));
return result;
}
}
免责声明 我刚刚为我的项目写了这篇文章,因为这里的答案并不令人满意。如有错误,稍后编辑。
我认为值得一提的是,.NET Core 3 中添加了连接跨度的重载,并且对 .NET Core 2.1 的支持已于 2021 年 8 月 21 日结束 [src]。如果你现在升级那么你可以简单地使用 String.Concat.
var hello = "Hello".AsSpan();
var space = " ".AsSpan();
var world = "World".AsSpan();
// .NET Core 3+
var result = string.Concat(hello, space, world);