Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Concatenate ReadOnlySpan<char>

Ok, .NET Core 2.1 has landed. With it we've gotten a new way to work with string data being ReadOnlySpan<char>. It's great at splitting string data, but what about combining the spans back together?

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

var result = ...; // How do I get "Hello World" out of the 3 above?
like image 544
hazzik Avatar asked May 31 '18 23:05

hazzik


People also ask

How to Concat 3 strings in c#?

You concatenate strings by using the + operator. For string literals and string constants, concatenation occurs at compile time; no run-time concatenation occurs. For string variables, concatenation occurs only at run time.

How to Concat strings vb?

String concatenation is when you combine two or more strings into a single string variable. String concatenation is performed with the & symbol. Non-string values will be converted to string when using & . Always use & (ampersand) to perform string concatenation.

What does Concat do in c#?

Concat Method is used to concatenate one or more instances of String or the String representations of the values of one or more instances of Object. It always returns a concatenated string. This method can be overloaded by passing different types and number of parameters to it.


1 Answers

Here's an example of how the .NET team internally handles this for 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)));
            });
    }
}

If you'd like to avoid using unsafe and use something that's maybe easier to read, you could use something like:

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

Using ReadOnlySpan is pretty low level and optimized for speed and so how you do it will likely depend on your own situation. But in many situations, it's probably fine to go back to string interpolation and StringBuilder (or don't convert to ReadOnlySpan at all). So

var sb = new StringBuilder();
return sb
    .Append(hello)
    .Append(space)
    .Append(world)
    .ToString();

or

return $"{hello.ToString()}{space.ToString()}{world.ToString()}";
like image 145
Dan Friedman Avatar answered Oct 19 '22 20:10

Dan Friedman