连接ReadOnlySpan <char>

时间:2018-05-31 23:12:45

标签: 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?

6 个答案:

答案 0 :(得分:3)

另一个选择是使用string.Concat,它接受ReadOnlySpan作为参数。这是取自github

的实现
internal static unsafe string Concat(ReadOnlySpan<char> str0, ReadOnlySpan<char> str1, ReadOnlySpan<char> str2)
{
    var result = new string('\0', 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;
}

https://github.com/dotnet/runtime/blob/4f9ae42d861fcb4be2fcd5d3d55d5f227d30e723/src/libraries/Microsoft.IO.Redist/src/Microsoft/IO/StringExtensions.cs

答案 1 :(得分:2)

您可以使用类似=>

的缓冲区来实现
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();

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

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

答案 2 :(得分:2)

我编写并使用了以下扩展/工具方法来连接跨度:

   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;
        }
    }

免责声明,由于此处的答案不令人满意,我现在为我的项目写了这个。如果有任何错误,请稍后进行编辑。

答案 3 :(得分:2)

我认为值得一提的是,.NET Core 3中添加了用于连接范围的重载,并且对.NET Core 2.1的支持很快就会在2021年8月21日终止(-ish)[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);

答案 4 :(得分:1)

以下是.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()}";

答案 5 :(得分:1)

我星期五下午的快速想法:

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

的快速阅读