What is ref struct in definition site
This addition to C# 7.2 is not really a feature in the sense of adding or enabling any new capability in the so-marked value-type itself, Rather, it allows the developer to declare or publish a specific restriction which governs the allowable use of that type everywhere else.
[edit: see span-safety at the github/dotnet site]
So instead of considering what ref struct
designation gives the end-user of the struct, consider how it benefits the author. Adding any restriction on external use logically entails a related guarantee that the ref struct
thus assumes, so the effect of the keyword is to empower or "license" the ref struct
to do things that require those specific guarantees.
The point is that it's an indirect benefit, because the kinds of operations that are typically considered licensed by ref struct
are basically none of the keyword's concern, and could be implemented and attempted, perhaps successfully even, by wily code anywhere, regardless of the ref struct
marking (or not).
So much for the theoretical part. In reality, what is the "wily code" use-case that so existentially depends on the additional guarantees, even to the extreme point of accepting all the accompanying limitations? Essentially, it's the ability for a struct
to expose a managed reference to itself or one of its fields.
Normally, C# enforces strong restrictions against the this
reference leaking out of any instance method of a struct
:
error CS8170: Struct members cannot return 'this' or other instance members by reference
The compiler has to be certain that there's virtually no possibility for the this
pointer of a struct to leak out of the context where it "sits." This is possible, quite likely, or even inevitable for any struct instance that exists within in a GC object, or has been temporarily boxed for the purpose of calling one of its instance methods.
note: When anticipated, such cases can be controlled-for in advance by associating a
GetPinnableReference
GC instance relative to which the managed pointer to thestruct
(or its interior) might be taken. Explicit pinning is the strictest and best-known technique, but there are also less heavy-handed options such as so-called managed tracking references, which are one of the most amazing under-recognized features of .NET... But these all add overhead, and in most situations, given that you're using value types in the first place, reducing the amount of cruft is desirable.
With all the ref
enhancements in recent years, C# now goes to even further lengths to detect and disallow this
from escaping. For example, in addition to the above, we now have:
error CS8157: Cannot return 'x' by reference because it was initialized to a value that cannot be returned by reference
and related errors such as...
error CS8374: Cannot ref-assign 'foo' to 'p' because 'foo' has a narrower escape scope than 'p'.
Sometimes the underlying reason for the compiler asserting CS8157
can be convoluted or hard to see, but the compiler hews to the conservative "better safe than sorry" approach, and this can sometimes result in false positives where, for example, you have additional special knowledge that the escape is ultimately contained within the stack.
For cases where CS8157
is genuinely unwarranted (i.e., in consideration of information that the compiler wasn't able to infer), these represent the "'perhaps even successful' wily code" examples I alluded to earlier, and generally there's no easy workaround, especially not via ref struct
. That's because the convoluted false-positives often only arise in higher-level ref
-passing code scenarios that would never be able to adopt the extreme limitations that are enforced for ref struct
in the first place.
Instead, ref struct
is used for very simple value-types. By guaranteeing them that their this
reference will always be anchored in an upper stack frame--and thus crucially, will never be awash in the GC heap--such types thus gain the confidence to publish managed pointers to themselves or their interiors.
Remember, however, that I said ref struct
is agnostic about how, why, and what the relaxations it provides are used for. What I was specifically alluding to there is that unfortunately, using ref struct
does not make CS8157
go away (I consider this a bug, see here and here).
Because ref struct
code that should properly be allowed to return its own this
is still prevented by the compiler from doing so, you have to resort to some rather brutish techniques to get around the fatal error(s) when coding within the supposedly liberated ref struct
instance methods. To wit, value-type instance methods written in C# that legitimately need to override fatal errors CS8170
/CS8157
can opaque the 'this' pointer by round-tripping it through an IntPtr
. This is left as an exercise for the reader, but one way to do this would be via the System.Runtime.CompilerServices.Unsafe package.
Just adding a bit to the other answer. Basically, they have created a ref struct to be able to hold a managed pointer as a member. This means that it cannot be garbage collected, and if it ever ended up on the heap, the GC would crash. The strange restrictions on what you can and can't do with it are all to do with this (as outlined in the microsoft docs here):
Microsoft docs on reference semantics in C# 7.2
All of which is completely fascinating, but doesn't really explain why on earth they've provided this functionality. The real reason was to allow apis that handle both managed and unmanaged memory to have a common interface (i.e. remove the need for endless overloads).
This is explained in detail in this blog:
Adam Sitnik on Span<T>
After some research, I stumbled upon this article on Compile time enforcement of safety for ref-like types in C# 7.2.
This C# feature is also known as “interior pointer” or “ref-like types”. The proposal is to allow the compiler to require that certain types such as
Span<T>
only appear on the stack.
The site also states the benefits of doing so, mainly concerning garbage collection and stack allocation.
Using ref-like types also brings some restrictions with it such as:
- ref-like type cannot be a type of an array element
- ref-like type cannot be used as a generic type argument
- ref-like variable cannot be boxed
- ref-like type cannot be a field of ordinary not ref-like type
- ref-like types cannot implement interfaces
- indirect restrictions, such as disallowed use of ref-like types in async methods, which are really a result of disallowing ref-like typed fields.
This limits them to be used for parameters, local variables, and in some cases return values.
There also exists an official documentation from Microsoft, as @UnholySheep pointed out in the comments.