Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why ReadOnlySpan may not be used as a type argument for generic delegates and generic methods?

Tags:

c#

dry

I understand why ReadOnlySpan may not be used as a type argument for generic classes. ReadOnlySpan is stack only and therefore it cannot be used as field types, field members live in the heap like its container object. However return values and arguments are always stack only, so why ReadOnlySpan cannot be used as type argument for generic delegates and generic methods?

Here you have an example illustrating what I'm saying:

using System;

namespace ConsoleApp7
{
    class Program
    {
        public delegate TResult MyFunc<TResult>(ReadOnlySpan<char> arg);

        static int GetSpanLength(ReadOnlySpan<char> span)
        {
            return span.Length;
        }

        static void Main(string[] args)
        {
            var span = "hello".AsSpan();

            MyFunc<int> func1 = GetSpanLength;
            var result1 = DoSomething(func1, span);

            // The type 'ReadOnlySpan<char>' may not be used as a type argument
            Func<ReadOnlySpan<char>, int> func2 = GetSpanLength;

            //The type 'ReadOnlySpan<char>' may not be used as a type argument
            var result = DoSomething<int, ReadOnlySpan<char>>(func2, span);


        }

        static TResult DoSomething<TResult, T>(Func<T, TResult> func, T arg)
        {
            return func(arg);
        }

        static TResult DoSomething<TResult>(MyFunc<TResult> func, ReadOnlySpan<char> arg)
        {
            return func(arg);
        }

    }
}

It's very unfortunate because it forces me to have two identical versions of DoSomething method, making my code very WET.

NOTE: for projects targeting .NET Framework you need to install System.Memory Nuget Package.

like image 692
Jesús López Avatar asked Nov 05 '18 13:11

Jesús López


1 Answers

This post from Adam Sitnik says

Let’s consider following C# code:

Span<byte> Allocate() => new Span<byte>(new byte[256]);

void CallAndPrint<T>(Func<T> valueProvider) // no generic requirements for T
{
    object value = valueProvider.Invoke(); // boxing!

    Console.WriteLine(value.ToString());
}

void Demo()
{
    Func<Span<byte>> spanProvider = Allocate;
    CallAndPrint<Span<byte>>(spanProvider);
}

As you can see the non-boxing requirement can not be ensured today if we allow the Span to be generic type argument. One of the possible solutions could be to introduce new generic constraint: stackonly. But then all the managed compilers would have to respect it and ensure the lack of boxing and other restrictions. This is why it was decided to simply forbid using Span as a generic argument.

like image 106
Jesús López Avatar answered Oct 18 '22 20:10

Jesús López