如何从 Span<T> 创建 Memory<T>?

17

我想重载一个解析方法,除了使用字符串(string)版本之外,还要使用ReadOnlySpan<char>参数。问题是,实现使用了Dictionary<string, T>进行解析逻辑。

我尝试将其更改为Dictionary<ReadOnlySpan<char>, T>,但当然无法工作,因为ReadOnlySpan<char>不允许作为泛型参数,因为它是一个仅在堆栈中存在的对象。然后我切换到使用允许的ReadOnlyMemory<char>。我实现了一个基本的Ordinal比较器,但现在遇到麻烦,即如何从ReadOnlySpan<char>参数创建ReadOnlyMemory<char>。这可能吗?

更新

看来这是不可能的。为了支持上面我发布的场景,我将更改字典以具有int键,该键是ReadOnlySpan<char>的哈希码,并使值成为一个列表,在其中嵌入字符串并手动解决哈希码冲突。


System.MemoryExtensions类中应该有一个AsMemory()方法来完成此操作,但由于在移动设备上无法测试。 - Sami Kuhmonen
不幸的是,AsMemory() 只接受 string、数组和 ArraySegment<T> - TylerBrinkley
1
由于 Spans 存储在堆栈上,您必须使用 ToArray() 方法将其复制到堆上。数组会隐式转换为 ReadOnlyMemory。 - Skarllot
2个回答

18

TL;DR:

你可以将Memory<T>转换为Span<T>,但不能反过来。

背景:

2018年的一篇MSDN Magazine文章介绍了Span<T>,并提供了有关此新功能的详细信息。 (原文链接)

Span<T> instances can only live on the stack, not on the heap. This means you can’t box spans (and thus can’t use Span<T> with existing reflection invoke APIs, for example, as they require boxing). It means you can’t have Span<T> fields in classes, or even in non-ref-like structs. It means you can’t use spans in places where they might implicitly become fields on classes, for instance by capturing them into lambdas or as locals in async methods or iterators (as those “locals” may end up being fields on the compiler-generated state machines.) It also means you can’t use Span<T> as a generic argument, as instances of that type argument could end up getting boxed or otherwise stored to the heap (and there’s currently no “where T : ref struct” constraint available).

...

You can create a Memory<T> from an array and slice it just as you would a span, but it’s a (non-ref-like) struct and can live on the heap. Then, when you want to do synchronous processing, you can get a Span<T> from it, for example:

static async Task<int> ChecksumReadAsync(Memory<byte> buffer, Stream stream)
{
  int bytesRead = await stream.ReadAsync(buffer);
  return Checksum(buffer.Span.Slice(0, bytesRead));
  // Or buffer.Slice(0, bytesRead).Span
}
static int Checksum(Span<byte> buffer) { ... }

我认为他的文章比我自己写的回答更清楚明白。


4
请将短单词放在前面,以节约他人时间。 - huang
1
有趣的是,Memory<T>public Span<T> Span { get; } 属性,但与 Span<T>ReadOnlySpan<T> 不同,它没有一个 implicit operator Span<T>( Memory<T> self ) 运算符。 - Dai

2

如果 T 是一个非托管类型,则可以从 Span<T> 转换为 Memory<T>。但要注意,您创建的 Memory<T> 对象应该手动执行 C# 编译器在 Spans 上保证的所有约束。

// data is Span<T> or ReadOnlySpan<T>
fixed (T* pointer = &data.GetPinnableReference())
{
     using var memoryManager = new UnmanagedMemoryManager<T>(pointer, data.Length);
     var memory = memoryManager.Memory;
     // you must use the memory object inside this fixed block!
}

UnmanagedMemoryManager 可在 nuget 包 Pipelines.Sockets.Unofficial 中找到(我不维护此包)。但如果您希望以不同方式构建它,也可以编写自己的未管理的 MemoryManager<T> 实现,具体请参考此处


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接