如何在不使用unsafe关键字的情况下获取结构体的Span<byte>视图

17
如何在不使用 unsafe 关键字的情况下,从单个结构体值创建一个 Span<byte> 视图(重解释型转换),且无需复制、无需分配内存。目前我只能使用 unsafe 关键字实现此功能:
public unsafe Span<byte> AsSpan<T>(in T val) where T : unmanaged
{
    void* valPtr = Unsafe.AsPointer(ref Unsafe.AsRef(val));
    return new Span<byte>(valPtr, Marshal.SizeOf<T>());
}

// Alternatively, slightly easier when using 'ref' instead of 'in'
public unsafe Span<byte> AsSpan<T>(ref T val) where T : unmanaged
{
    void* valPtr = Unsafe.AsPointer(ref val);
    return new Span<byte>(valPtr, Marshal.SizeOf<T>());
}

当处理数组而不是单个值时,可以使用MemoryMarshal.Cast<TTo, TFrom>( ... )轻松且安全地完成,例如:

public Span<byte> AsSpan<T>(Span<T> vals) where T : unmanaged
{
    return MemoryMarshal.Cast<T, byte>(vals);
}

使用 netstandard2.0,最新的语言版本 C# 7.3,以及最新的RC包 System.Memory 和 System.Runtime.CompilerServices.Unsafe:
<PropertyGroup>
   <TargetFramework>netstandard2.0</TargetFramework>
   <LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup>
   <PackageReference Include="System.Memory" Version="4.5.0" />
   <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.0" />
</ItemGroup>

编辑: 关于内存安全/损坏的回复 - C# 7.3中引入的unmanaged泛型约束可以替代struct泛型约束,以安全的方式完成此操作。
请参见:https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters

where T : unmanaged类型参数不能是引用类型,并且在任何嵌套级别上也不能包含任何引用类型成员。


如果我将某个类的字段的引用传递到您的不安全版本中,然后实例被GC重新定位,会发生什么? - Evk
这可能会导致内存损坏。你希望我在帖子中添加免责声明吗? - zone117x
@Evk C# 7.3中的新“未托管”泛型约束可用于防止您所描述的GC情况。 - zone117x
谢谢,好知道。我并不希望有免责声明,只是提醒一下以防你没有意识到这不是通用解决方案。 - Evk
那么如果我们有一个包含引用类型字段的结构体,比如 byte[],就无法实现这个功能了吗? - joe
2个回答

20

部分解决方案:

如果目标是 netcoreapp 而不是 netstandard2.0,则可以在 netcoreapp2.1 中使用 API(截至本评论日期,可在此处下载)。


用法:

using System.Runtime.InteropServices;

public Span<byte> AsSpan<T>(ref T val) where T : unmanaged
{
    Span<T> valSpan = MemoryMarshal.CreateSpan(ref val, 1);
    return MemoryMarshal.Cast<T, byte>(valSpan);
}

这并不是针对netstandard2.0中所需功能的解决方案。尽管如此,这对于许多人来说应该是有帮助的。


0

对我来说,你们两个不安全的方法都可能会破坏内存。因此,在没有适当的限制条件下,不应该使用它们。

1)如果结构体是在堆栈上分配的,则应仅在同一堆栈中更深层次地使用返回的 span。如果将其返回给调用方法,则会得到无效指针。

2)如果结构体在堆中,则 GC 可以移动包含引用类型的结构体。您的 span 随时可能被破坏。请在容器实例上使用 fixed。

3)如果结构体是在本机内存中分配的,则有太多选项...


我更新了原始问题,使用内存安全的“unmanaged”约束来解决这个问题。这个顶级答案已经不再相关,并且已经在顶级问题的评论中讨论过了。 - zone117x
1
顺便提一下,使用MemoryMarshal.AsBytes比Cast略好。 - Anatoly Zhmur
如果一个结构体是引用类型的字段,那么它仍然存在于堆上,而垃圾回收可能会移动它。你仍然需要以某种方式“固定”它,无管理约束在这种情况下没有帮助,对吗?@zone117x - joe
1
我刚刚查看了CoreFX源代码。虽然我并不完全理解发生了什么,但我注意到span在内部使用类似指针的ref T,因此它可以以某种方式“跟随”它所引用的结构体的GC重定位。 - joe

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