I'm implementing a stack based vector type like this:
[StructLayout(LayoutKind.Sequential)]
public struct VectorI16x8 {
public short s0, s1, s2, s3, s4, s5, s6, s7;
public short this[int i] {
get => UnsafeValues[i];
set => UnsafeValues[i] = value;
}
public int Length => 8;
private Span<short> UnsafeValues => MemoryMarshal.CreateSpan(ref s0, Length);
}
It's pretty clear that if I made UnsafeValues
public, it would be possible for values of this struct to be moved in memory (even without the garbage collector), leading to a dangling reference within the Span
or causing the Span
to point to an incorrect instance.
However, what is less clear is whether using a span like this privately within my struct implementation is safe. My question is: can I assume that my struct's this
will not move during the execution of my indexer (meaning that the span created by UnsafeValues
will remain valid) or would I have to use a fixed
block to make this safe?
You do not require a fixed
block for this; the short version is that the GC is required to handle this scenario. Spans are fundamentally no different to any other "interior pointer" - and the same problem could be initiated by some method in your struct invoking DoSomething(ref s0)
, DoSomethingElse(in s1)
, etc. This is part of why managed pointers (ref
, Span<T>
, etc) can only be stored on the stack, not as fields on types (except for ref struct
) - so that the GC knows where to check for them (it needs to walk the stackframes anyay, to check for fixed
markers, etc - which are just special tokens on locals). Whether the GC handles this by deciding not to move those regions (as though they were fixed
), or to fixup the references (during a pause) is an implementation detail of the GC.
(minor note: depending on the runtime, there is a 3-field span or a 2-field span; they are engineered differently depending on the capabilities of the runtime to handle this scenario, but either way the short version is "magic happens" and it works without you needing to panic)