连接ReadOnlySpan <char>

haz*_*zik 5 c# .net-core .net-core-2.1

好的,.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?
Run Code Online (Sandbox Code Playgroud)

Dan*_*man 10

以下是 .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)));
            });
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您想避免使用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();
}
Run Code Online (Sandbox Code Playgroud)

使用ReadOnlySpan级别非常低,并且针对速度进行了优化,因此您如何使用可能取决于您自己的情况。但在许多情况下,回到string插值和StringBuilder(或根本不转换ReadOnlySpan)可能很好。所以

var sb = new StringBuilder();
return sb
    .Append(hello)
    .Append(space)
    .Append(world)
    .ToString();
Run Code Online (Sandbox Code Playgroud)

或者

return $"{hello.ToString()}{space.ToString()}{world.ToString()}";
Run Code Online (Sandbox Code Playgroud)


Kal*_*ten 6

你可以用这样的缓冲区来实现=>

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();
Run Code Online (Sandbox Code Playgroud)

您可以通过使用 arraypool 重用缓冲区来再次优化

char[] buffer =  ArrayPool<char>.Shared.Rent(hello.Length + space.Length + world.Length);
// ...
ArrayPool<char>.Shared.Return(buffer);
Run Code Online (Sandbox Code Playgroud)

  • ArrayPool 没有任何好处。实际上它更慢(对于这些字符串)并分配额外的 8 个字节。 (3认同)
  • 确实如此,但在长期存在的应用程序中,使用 ArrayPool 是有意义的,因此您不必一遍又一遍地重新分配这些字节。根据应用程序的不同,它可能会产生显着的差异。 (2认同)

Ste*_*ens 5

我认为值得一提的是,在 .NET Core 3 中添加了用于连接 span 的重载,并且对 .NET Core 2.1 的支持很快就会结束(-ish),无论如何都会在 2021 年 8 月 21 日 [ src ]。如果您现在升级,那么您可以简单地使用String.Concat

https://docs.microsoft.com/en-us/dotnet/api/system.string.concat?view=netcore-3.1#System_String_Concat_System_ReadOnlySpan_System_Char__System_ReadOnlySpan_System_Char__System_ReadOnlySpan_System_Char__

var hello = "Hello".AsSpan();
var space = " ".AsSpan();
var world = "World".AsSpan();

// .NET Core 3+
var result = string.Concat(hello, space, world);
Run Code Online (Sandbox Code Playgroud)