需要一种解决方法来访问<T>返回 IEnumerable 的函数中的 ReadOnlySpan。



在处理本机代码互操作时,我决定是时候学习和尝试c#语言的新Span特性了。尽管经过多次试验,一切都进行得非常顺利,直到我进入我的长函数的最后阶段,我在下面插入了一个最小的可重复的样本:

[DllImport(dll, SetLastError = true)]
internal static extern void GetNativeData(out byte lpBuffer, int size, out bytesRead);
ReadOnlySpan<T> ReadArray<T>(ReadOnlySpan<byte> buf, int Length) where T : unmanaged
{
var size = Length * Unsafe.SizeOf<T>();
if (buf.Length < size)
buf = new byte[size];
GetNativeData(out MemoryMarshal.GetReference(buf), size, out int read));
Dh.CreateError(ReadMemoryErr);

return MemoryMarshal.Cast<byte, T>(buf.Slice(0, size));
}
static IEnumerable<MyClass> GetResult()
{
// Here I allocate a buffer
Span<byte> buf = new byte[1000];
// After a long serie of calls to unmanaged DLL functions I end up with something like this:
ReadOnlySpan<uint> uintRes = data.ReadArray<uint>(buf, 10);
ReadOnlySpan<ushort> shortRes = data.ReadArray<ushort>(buf, 10);
for (int i = 0; i < uintRes.Length; i++)
{
// Any access to spans inside this loop result in Error CS4013
string r = GetFunRes(uintRes[i]);
IntPtr r2 = GetFunRes2(shortRes[i]);
yield return new MyClass() { Prop1 = r, Prop2 = r2 };
}
}
我得到的错误是

错误CS4013:类型为'Span'的实例不能在嵌套函数、查询表达式、迭代器块或异步方法

现在,我读到有解决这个问题的方法。这篇文章只展示了异步方法的用法,但也说明了它适用于迭代器。不幸的是,我没能做到这一点。我只需要读取span的特定元素,然后生成一个不包含任何元素或对span的引用的结果。只是,无论我尝试什么,只要我试图访问一些编译器失败。

我读过关于Memory<T>的文章,可能这个可以工作,但是我有一些担心,因为我读到性能大大降低了。人们也首先推荐Span。我希望我能找到一个解决方案,否则我将不得不从头开始我的项目并重写所有内容,因为现在与Span紧密相连。

提前感谢您的帮助

@00110001:

var uintRes = data.ReadArray<uint>(buf, 10).ToArray();

我知道这会起作用,但我认为它会使Span使用无用,以及我试图利用(ReadArray<T>)的新泛型非托管功能。如果我没记错的话,调用ToArray()和旧的封送样式是一样的,每次调用都会创建一个新的副本:

internal static extern void GetNativeData(out uint[] lpBuffer, int size, out bytesRead);

@Ian坎普我是这样尝试文章中提到的解决方法的:

int len = uintRes.Length;
for (int i = 0; i < len; i++)
{
var res = ParseData(i);
if (res == ExpectedResult())
yield return res;
}
MyClass ParseData(int index)
{
// CS8175: Cannot use ref local 'uintRes, shortRes' inside an anonymous method, lambda expression, or query expression
string r = GetFunRes(uintRes[index]);
IntPtr r2 = GetFunRes2(shortRes[index]);
return new MyClass() { Prop1 = r, Prop2 = r2 };
}

我要把这个问题标记为closed因为通过阅读这篇文章,我自己找到了答案。基本上,为了使用IEnumerable和yield,需要Memory<T>,因为Span<T>是一个在堆栈上分配的refstruct,不能跨yield边界使用。另一方面,Memory<T>可以驻留在堆上,同时仍然提供Span<T>访问。本文的作者展示了纯Span所能做到的最大限度,以及在LINQ中启用IEnumerable所需的样例代码,以及不同实现的基准测试。与其他解决方案相比,IEnumerable+memory<T>非常慢,但对于仍然想要LINQ的人,我建议看看NetFabric。Hyperlinq, LINQ的替代版本,由本文的同一作者开发,以填补这一空白。我不确定即使使用这种解决方案,是否仍然会与for/foreach循环结合纯Span存在很大差异,但我认为这主要取决于调用的数量,查询的类型等(请参阅linqbenchmark以查看每个查询的执行情况)。由于

注。我注意到有时托管文章的服务器无法访问,因此我在这里包含了一个web存档捕获,以确保将来始终可以访问。

相关内容

  • 没有找到相关文章

最新更新