unsafeコンテキスト以外でIntPtrからSpan<T>またはReadOnlySpan<T>に無理矢理変換する方法


How to force conversion from IntPtr to Span<T> or ReadOnlySpan<T> without unsafe context

以前の記事IntPtrからSpan/ReadOnlySpan に変換できないこと書いたが、無理矢理なんとかしてみる。

力技といえば動的コード生成。

ソースコード

多分こんなので動くはず。

using System;
using static System.Linq.Expressions.Expression;

namespace IntPtrToSpan
{
    public static class IntPtrToSpanExtention
    {
        public static Span<T> AsSpan<T>(this IntPtr pointer, int length) =>
            //return new Span<T>(pointer.ToPointer(), length);
            Impl<T>.Delegate(pointer, length);
        public static ReadOnlySpan<T> AsReadOnlySpan<T>(this IntPtr pointer, int length) =>
            //return new ReadOnlySpan<T>(pointer.ToPointer(), length);
            Impl<T>.ReadOnlyDelegate(pointer, length);

        private delegate Span<T> AsSpanDelegate<T>(IntPtr pointer, int length);
        private delegate ReadOnlySpan<T> AsReadOnlySpanDelegate<T>(IntPtr pointer, int length);
        static class Impl<T>
        {
            private static Func Generate<Func>(bool readOnly)
            {
                var pointer = Parameter(typeof(IntPtr), "pointer");
                var length = Parameter(typeof(int), "length");

                var constructor = (readOnly ? typeof(ReadOnlySpan<T>) : typeof(Span<T>)).GetConstructor(new[] { typeof(void*), typeof(int) });
                var lambda = Lambda<Func>(
                    New(constructor,
                        Call(
                            pointer,
                            typeof(IntPtr).GetMethod(nameof(IntPtr.ToPointer), Type.EmptyTypes)),
                        length),
                    pointer,
                    length
                    );
                return lambda.Compile();
            }
            public static AsSpanDelegate<T> Delegate = Generate<AsSpanDelegate<T>>(false);
            public static AsReadOnlySpanDelegate<T> ReadOnlyDelegate = Generate<AsReadOnlySpanDelegate<T>>(true);
        }
    }
}

個人的なハマりポイント

  • Func の型引数で ref struct を指定できないので、デリゲートを定義した。
  • Lambdaのパラメータと式中のExpressionで別々のインスタンスを使用していたら、InvalidOperationException: variable 'someVariable' of type 'SomeType' referenced from scope '', but it is not definedって例外が出て「定義されているやろ」って悩んだ。
  • コンパイラの生成した式ツリーを参考にしようとExpression<Func<IntPtr,int, Span<T>>> a = (pointer, length) => new Span<T>(pointer.ToPointer(), length);をコンパイルしようとしたら、ref structを式ツリーに含められないようでコンパイルエラーになった。
  • typeof(void*) ってunsafeコンテキスト以外だとどうやっってとってきたらいいんだ?」って思ったら、unsafeコンテキストじゃなくても大丈夫だった。