当我在 C# 中将 ref 值分配给 out 参数时会发生什么?
What happens when I assign a `ref` value to an `out` argument in C#?
我正在编写一个 class 来检索二进制数据并支持将其一般地转换为基本类型。它应该尽可能高效。这是现在的样子:
public abstract class MemorySource {
public abstract Span<byte> ReadBytes(ulong address, int count);
public unsafe bool TryRead<T>(ulong address, out T result) where T : unmanaged {
Span<byte> buffer = ReadBytes(address, sizeof(T));
result = default;
// If the above line is commented, `result = ref <...>` won't compile, showing CS0177.
if (!buffer.IsEmpty) {
result = ref Unsafe.As<byte, T>(ref buffer.GetPinnableReference());
return true;
} else
return false;
}
}
由于我正在使用大量内存,而且我的代码将执行大量小的读取操作。我想尽量减少内存被复制的次数。
ReadBytes
实现将 a) 创建一个跨越堆上已有数组的部分,或者 b) stackalloc
一个缓冲区并用来自远程源的数据填充它(取决于我将使用的数据)。关键是,它不会自己在堆上分配任何东西。
我希望我的 TryRead<T>
方法 return 对跨度内存的类型化引用,而不是将该内存复制到新值中,我想知道这是否可能。我注意到我无法在不初始化的情况下将 ref
值分配给 out
参数,但之后我可以,如果我们假设我正在分配一个引用,这就没有什么意义了。
我想我想问的是,这段代码到底发生了什么?我 return 是对现有值的引用,还是将该值复制到新的堆栈分配值中?堆栈分配跨度和堆分配跨度的行为有何不同?如果使用堆分配跨度,GC 会知道在移动数据时更新类型 T
的引用吗?
无论如何,原始类型都是值类型,因此您在读取它们时不必担心在堆上进行分配。
您不能对这段代码使用 stackalloc
,因为您不能(或不应该尝试)return 指向它的指针,因为它将在结束时被销毁功能。
您目前拥有的代码是危险的,因为您return正在使用实际上并未固定的可固定参考。
您在使用 ref
参数时遇到问题的原因是因为在 else
中您根本没有分配它。您应该将 result = default;
行移动到 else
分支。
无论哪种方式,你最好使用 MemoryMarshal
来完成所有这些,请注意,这不需要不安全的代码
public bool TryRead<T>(ulong address, out T result) where T : unmanaged
{
ReadOnlySpan<byte> buffer = ReadBytes(address, sizeof(T));
if (!buffer.IsEmpty)
{
result = MemoryMarshal.Read<T>(buffer);
return true;
}
result = default;
return false;
}
我正在编写一个 class 来检索二进制数据并支持将其一般地转换为基本类型。它应该尽可能高效。这是现在的样子:
public abstract class MemorySource {
public abstract Span<byte> ReadBytes(ulong address, int count);
public unsafe bool TryRead<T>(ulong address, out T result) where T : unmanaged {
Span<byte> buffer = ReadBytes(address, sizeof(T));
result = default;
// If the above line is commented, `result = ref <...>` won't compile, showing CS0177.
if (!buffer.IsEmpty) {
result = ref Unsafe.As<byte, T>(ref buffer.GetPinnableReference());
return true;
} else
return false;
}
}
由于我正在使用大量内存,而且我的代码将执行大量小的读取操作。我想尽量减少内存被复制的次数。
ReadBytes
实现将 a) 创建一个跨越堆上已有数组的部分,或者 b) stackalloc
一个缓冲区并用来自远程源的数据填充它(取决于我将使用的数据)。关键是,它不会自己在堆上分配任何东西。
我希望我的 TryRead<T>
方法 return 对跨度内存的类型化引用,而不是将该内存复制到新值中,我想知道这是否可能。我注意到我无法在不初始化的情况下将 ref
值分配给 out
参数,但之后我可以,如果我们假设我正在分配一个引用,这就没有什么意义了。
我想我想问的是,这段代码到底发生了什么?我 return 是对现有值的引用,还是将该值复制到新的堆栈分配值中?堆栈分配跨度和堆分配跨度的行为有何不同?如果使用堆分配跨度,GC 会知道在移动数据时更新类型 T
的引用吗?
无论如何,原始类型都是值类型,因此您在读取它们时不必担心在堆上进行分配。
您不能对这段代码使用 stackalloc
,因为您不能(或不应该尝试)return 指向它的指针,因为它将在结束时被销毁功能。
您目前拥有的代码是危险的,因为您return正在使用实际上并未固定的可固定参考。
您在使用 ref
参数时遇到问题的原因是因为在 else
中您根本没有分配它。您应该将 result = default;
行移动到 else
分支。
无论哪种方式,你最好使用 MemoryMarshal
来完成所有这些,请注意,这不需要不安全的代码
public bool TryRead<T>(ulong address, out T result) where T : unmanaged
{
ReadOnlySpan<byte> buffer = ReadBytes(address, sizeof(T));
if (!buffer.IsEmpty)
{
result = MemoryMarshal.Read<T>(buffer);
return true;
}
result = default;
return false;
}