Search code examples
c#.netperformance.net-standard-2.0ref-struct

string.AsSpan() vs implicit cast operator performance in .NET for ReadOnlySpan<char>


Since .NET version something we now have an implicit cast from string -> ReadOnlySpan<char>. This means that if a function accepts a ReadOnlySpan<char>, we can just pass it a string and the "conversion" will happen automatically.
This implicit operator is not available in .NET Standard 2.0 nor in .NET Framework though... We are working with all three :)

Basically I have this field in my ref struct

private readonly ReadOnlySpan<char> _defaultDelimiters = new[] { ' ', ';' };

I use this field every time a function called Read() is called. The alternative would be to remove this field and put a stackalloc inside Read(). But I do not know if that is wise or not

To make my .NET code compliant with .NET Standard 2.0 and Framework I could simply add .AsSpan() calls to my strings. This is however "unnecessary" in .NET (Core) but my question is: Is it also performance wise worse? Or do the .AsSpan() extension and the implicit operator perform the same operation behind the scenes anyway?


Solution

    1. As you can see from the source code, the following code is executed for the implicit operator:

      public static implicit operator ReadOnlySpan<char>(string? value) =>
              value != null ? new ReadOnlySpan<char>(ref value.GetRawStringData(), value.Length) : default;
      

      The extension method does this

      public static ReadOnlySpan<char> AsSpan(this string? text)
      {
          if (text == null)
              return default;
      
          return new ReadOnlySpan<char>(ref text.GetRawStringData(), text.Length);
      }
      

      So effectively the exact same code. There is absolutely no difference in performance between these. (Possibly a small difference in branch prediction due to the opposite order of the conditions).

    2. No, there is no heap allocation. A Span and a ReadOnlySpan are ref struct so they can't be allocated on the heap even if you tried (like boxing them), it's not allowed.

    3. Assuming the char array is static (or a string is coming from a constant value), probably you are already using the most efficient allocation method. It doesn't need to reserve any stack space, it can just take a Span off the existing array.
      Whereas stackalloc means you need to copy the data into it every time. And stackalloc can also have serious implications when used in a loop, often causing a stack overflow.
      If the char array is not static (eg you are feeding new[] straight into the initializer/constructor) then that is going to be even more inefficient. Keep the array static in a separate field, or just use a constant string value.