以下代码使编译器在属性getter的第一行中引发错误CS1605(“因为它是只读的,所以无法将'var'作为ref或out参数传递”)。
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct MyStruct
{
public readonly int Field1;
public readonly int Field2;
public MyStruct(int field1, int field2) => (Field1, Field2) = (field1, field2);
public ReadOnlySpan<byte> Span
{
get
{
// This code only works when MyStruct is not read only
ReadOnlySpan<MyStruct> temp = MemoryMarshal.CreateReadOnlySpan(ref this, 1);
return MemoryMarshal.Cast<MyStruct, byte>(temp);
}
}
}
从readonly
行中删除public readonly struct MyStruct
可以使代码正常工作,但是出于性能原因,我真的希望结构为readonly
。与必须始终以ref
传递结构相比,它使代码更加简洁。
是否可以从只读结构中获取ReadOnlySpan<byte>
?
编辑: ... ...而不创建结构的隐式或显式副本?
答案 0 :(得分:5)
看起来像这样:
// The following code will work from C# 7.3 and up, no unsafe keyword required
Span<MyStruct> span = stackalloc MyStruct[1];
span[0] = new MyStruct(3, 4);
var bytes = MemoryMarshal.Cast<MyStruct, byte>(span);
如果我们想将其公开为属性,可以尝试以下操作:
// Will not work at runtime
public ReadOnlySpan<byte> Span
{
get
{
unsafe
{
fixed (MyStruct* ptr = &this)
{
return new ReadOnlySpan<byte>(ptr, sizeof(MyStruct)); // If on the heap, we're doomed as returning will unpin the memory.
}
}
}
}
并将结构标记为readonly ref struct
,这将再次保护我们曾经在堆上使用的结构。这会编译,但不会在运行时得到AccessViolationException
时运行。我将做更多的挖掘工作,看是否可行,从逻辑上讲应该是安全的,但今天可能是不可能的。
另一种折衷解决方案是将其保留为readonly struct
(而不是ref struct
)并添加此静态方法:
public static unsafe ReadOnlySpan<byte> GetSpan(ref MyStruct myStruct)
{
return new ReadOnlySpan<byte>(Unsafe.AsPointer(ref myStruct), sizeof(MyStruct));
}
然后从调用代码开始:
var myStruct = new MyStruct(1, 2);
var span = MyStruct.GetSpan(ref myStruct);
我们可以通过将其移到ref扩展方法(C#7.2功能)中来改进此方法的使用:
class Program
{
static void Main()
{
var myStruct = new MyStruct(1, 2);
var span = myStruct.GetSpan();
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct MyStruct
{
public readonly int Field1;
public readonly int Field2;
public MyStruct(int field1, int field2) => (Field1, Field2) = (field1, field2);
}
public static class MyStructExtensions
{
public static unsafe ReadOnlySpan<byte> GetSpan(ref this MyStruct myStruct)
{
return new ReadOnlySpan<byte>(Unsafe.AsPointer(ref myStruct), sizeof(MyStruct));
}
}
答案 1 :(得分:0)
下面的代码不安全。
请参阅Stuart评论:
这样做非常不安全,因为您不知道结构在哪里 生活(堆栈还是堆),如果您将其作为引用结构,则可以 保证它只是堆栈,并且无需假设即可安全地进行操作 您已经被一些不安全的代码预先锁定了。
MemoryMarshal.CreateReadOnlySpan
方法has a warning too。
现在是代码
您可以分两步实现。
span<T>
中,其中T
是结构的类型。将span<T>
投射到span<byte>
var s = new MutableStruct();
var span = MemoryMarshal.CreateReadOnlySpan(ref s, 1);
var byteSpan = MemoryMarshal.Cast<MutableStruct, byte>(span);
Console.WriteLine(string.Join("", byteSpan.ToArray().Select(v => $"{v:X2}"))); // 00000000
s.SetX(42);
Console.WriteLine(string.Join("", byteSpan.ToArray().Select(v => $"{v:X2}"))); // 2A0000000
Console.ReadKey();
// here the used struct
struct MutableStruct
{
private int _x;
public int X => _x;
public void SetX(int value) => _x = value;
}
您可以here对其进行测试
答案 2 :(得分:0)
如果查看MemoryMarshal.CreateReadOnlySpan的源代码,您会发现它只是通过传递 reference 和<而创建了 ReadOnlySpan 的新实例。 em> length 参数。
简单的解决方案是通过传递数组自己创建ReadOnlySpan实例:
ReadOnlySpan<MyStruct> temp = new ReadOnlySpan<MyStruct>(new MyStruct[] { this });
这将使用不同的ctor,但是实现方式相似,您将获得相同的结果。
编辑:
或使用不安全的方法:
public ReadOnlySpan<byte> Span
{
get
{
unsafe
{
return new ReadOnlySpan<byte>(Unsafe.AsPointer(ref this), 1);
}
}
}
答案 3 :(得分:0)
仅在此处添加通用解决方案,基于之前的答案:
public class StructExtensions
{
public static unsafe ReadOnlySpan<byte> AsSpan<T>(ref T myStruct) where T : struct
{
return MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref myStruct, 1));
}
}
你确实像这样使用它:
var p = StructExtensions.AsSpan<MyStruct>(ref this /* or whatever your variable is */);