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コンテキストじゃなくても大丈夫だった。