考虑以下代码:
public class SomeClass
{
public bool someBool;
}
public ref struct RefStructWithManagedFields
{
private SomeClass? _managedField;
internal void SetSomethingManaged(SomeClass something)
{
_managedField = something;
}
internal SomeClass? GetSomethingManaged()
{
return _managedField;
}
}
public unsafe ref struct RefStructWithPointer
{
/// emits warning CS8500:
/// "This takes the address of, gets the size of, or declares a pointer to a managed type ('RefStructWithManagedFields')
private readonly RefStructWithManagedFields* _pointer;
public ref RefStructWithManagedFields RefField { get => ref *_pointer; }
internal RefStructWithPointer(RefStructWithManagedFields* pointer)
{
_pointer = pointer;
}
}
class SomeContext
{
private SomeClass someClass = new();
public void DoSomething(ref RefStructWithPointer structWithPointer)
{
// set a managed field on a struct internally referenced with a pointer
structWithPointer.RefField.SetSomethingManaged(someClass);
}
}
class AnotherContext
{
public void DoSomething(ref RefStructWithPointer structWithPointer)
{
// get a managed field from a struct internally referenced with a pointer
SomeClass? someOtherClass = structWithPointer.RefField.GetSomethingManaged();
Console.WriteLine(someOtherClass?.someBool);
}
}
public class Program
{
static SomeContext someContext = new();
static AnotherContext anotherContext = new();
unsafe static void Main()
{
RefStructWithManagedFields foo = default;
/// emits warning CS8500:
/// "This takes the address of, gets the size of, or declares a pointer to a managed type ('RefStructWithManagedFields')
RefStructWithPointer bar = new(&foo);
someContext.DoSomething(ref bar);
/// garbage for the sake of argument
GeneratedALotOfGarbage();
GC.Collect();
anotherContext.DoSomething(ref bar);
}
}
Program
正在传递一个 ref struct
(RefStructWithPointer
),其中包含指向另一个带有托管字段 (ref struct
) 的 RefStructWithManagedFields
的指针。指针对外部是隐藏的,因为它以 ref property
的形式返回。从 C# 11.0 开始,这可以通过“ref fields”来完成,但是,我必须使用 9.0
据我了解,直接获取指向托管引用的指针是不安全的,因为 GC 可能会移动它,因此指针可能无效。如果您想要一个指针,则必须通过作用域内的
fixed
关键字“修复”它。
获取指向任何
struct
的指针也可能不安全,因为该结构可能作为另一个类的字段存在于堆中,因此如果您想要一个指针,也必须“修复”它。但是,如果它是本地范围的 var(堆栈分配),编译器将允许您避免修复它。
也允许使用指向
ref struct
的指针,无需修复,因为它可以确保位于堆栈中。
我不明白的是,当这些结构包含托管引用时,编译器会发出警告。我知道那些托管引用本身可以在堆上移动。但是,如果我们不直接使用指向它们的指针,而是指向包含它们的结构体。为什么很危险?
如果一个类通过 GC 移动,则必须更新对它的所有引用。因此,我假设
ref struct
中包含的托管引用也将得到更新。在任何安全上下文中,ref struct
中的引用始终是安全的。如果“指向的”结构存在于堆栈中,为什么可能无法通过指针(指向结构)访问它?结构体中包含的引用不会更新吗?
在提供的示例中,调用
GetSomethingManaged()
或 SetSomethingManaged()
是否可能存在危险?为什么?
我无法复制这种危险的场景。我不理解通过指针访问具有托管字段的引用结构背后的危险,我的理解让我觉得它是安全的,而实际上危险的是其他东西。
回复我自己。该警告的存在是由于“范围界定”。通过声明一个指针,即使它指向“引用结构”,如果像这样使用,您实际上可以允许它转义:
public class Program
{
static SomeContext someContext = new();
static AnotherContext anotherContext = new();
unsafe static void Main()
{
DoPointerThings(out var crash);
crash.RefField.GetSomethingManaged(); /// boom, we are pointing to a ref struct that is not longer allocated
}
private unsafe static void DoPointerThings(out RefStructWithPointer output)
{
RefStructWithManagedFields foo = default;
/// emits warning CS8500:
/// "This takes the address of, gets the size of, or declares a pointer to a managed type ('RefStructWithManagedFields')
output = new(&foo);
someContext.DoSomething(ref output); /// this is ok
anotherContext.DoSomething(ref output); /// also ok
}
}
所以,我的教训是:警告就是警告,只要小心一点,真正尝试打破它,看看你是否在做坏事