C# Futures: Read-Only References and Structs
In C++ we have a feature known as “const”. This can be applied to parameters so that the caller knows that function will not modify the parameter and/or the object the parameter references (it’s actually more complicated than this. For more information see Const Correctness). Under this proposal, C# would get something similar.
Read-Only Ref Parameters
Also known as “in parameters”, readonly references would allow similar restrictions in C#. The basic idea is that you can mark a parameter as “readonly ref” or just “in” and the compiler would understand it as “pass this parameter by reference to improve performance, but don’t allow it to actually be changed”. This feature would be of particular use for large structs in high-performance scenarios. The proposal cites the following examples:
Vector/matrix math operators in graphics libraries like XNA are known to have ref operands purely because of performance considerations. There is code in Roslyn compiler itself that uses structs to avoid allocations and then passes them by reference to avoid copying costs.
This syntax would combine both of the C++ versions of const. Neither the parameter itself can be modified, nor can any of the data in the object or struct it references.
Currently, you have to use the “ref” or “out” keyword when passing a parameter by reference. Under this proposal, that requirement won’t apply for “in” parameters. Furthermore, you could pass in the result of an expression (this is currently allowed by VB, not but C#).
Overloading rules will work in the same way as it does today, for ref vs out parameters.
It is still up for debate, but currently the plan is to not allow “in” parameters to be captured by anonymous or async functions (i.e. making a closure object). The problem with capturing an “in” parameter is that it will cause a copy to be made, which defeats the purpose of using “in” to avoid the performance cost of copies.
Marking a parameter as “readonly ref” or “in” does not make the value it refers to immutable. While the function declaring the parameter cannot make changes to it, the value can be changed elsewhere. This doesn’t require multiple-threads, just a way to access the original variable the parameter refers to.
Calling methods on a struct can be problematic. From the proposal:
Since all regular instance methods of a struct can potentially mutate the instance or ref-expose this, an intermediate copy must be created, as already a case when a receiver is a readonly field.
However, since there is no backward compatibility considerations and there are workarounds, the compiler should give a warning to ensure the implicit copying is noted by the user.
As with “out” parameters, a special attribute would indicate that an “in” parameter is desired. Older compilers would ignore this parameter so there is no backwards compatibility issue.
Read-Only Ref Returns
Closely related to this feature is the ability to mark ref returns as read-only. As with “in” parameters, the main reason for this feature is performance. However, in this case you would not be allowed to return the results of an expression. It has to be a legal variable for a normal ref return, which would include array elements, ref parameters, and fields in objects.
ref/in Extension Methods
“ref” extension methods would allow said extension method to modify the struct that is passed in. The compiler would need to be able to prove that an argument passed to a ref extension method would need to be mutable.
“in” extension methods would not allow for modifications, but would still be useful for performance sensitive code, especially if the struct is large. This would of course not require a mutable argument.
In both cases, this feature would only be available for structs.
Editors note: presumably if the Pure attribute were widely used, the compiler could then disallow calling non-pure methods for “in” extension methods. However, this would not improve performance so it isn’t as likely to happen.
Marking a struct variable as read-only can have performance ramifications. The compiler doesn’t know if a given method call can modify the struct, so it assumes that it will and always makes a copy of the readonly struct variable.
With this feature, you would be able to mark the whole struct as read-only at the type level. By making this promise, you tell the compiler that copies are not necessary when the readonly stuct is exposed via a readonly struct variable.
The proposal notes:
The only obvious question is whether there is a need for an option to opt out some of the methods as mutators.
So far it feels like per-member control over readonly is an unnecessary complication, which also can be added later if found to be necessary.
Current assumptions are that "Mixed" mutable/immutable structs are not common. Besides, even partially mutable struct variables would generally need to be LValues and thus would not impacted by the implicit copying.
The proposal notes that these features would be unlikely to benefit existing code, but could be useful in new scenarios such as:
- cloud/datacenter scenarios where computation is billed for and responsiveness is a competitive advantage.
- Games/VR/AR with soft-realtime requirements on latencies
It also warns that the function being called can circumnavigate the restrictions on “in” parameters, but that is a only minor concern because they can already do the same with “out” parameters.
Read-Only Ref Returns
I'd like to find out more about how/why this is a restriction for the feature. Is there a relevant GitHub issue link where this feature's being discussed?