Allow "modifying" the return value of a property getter with readonly setters #8364
Replies: 7 comments 11 replies
-
Add readonly modifier on your setter. var c = new C();
c.GetS().X = 2; // No error.
struct S(int[] array)
{
public int X
{
get => array[0];
readonly set => array[0] = value;
}
}
class C
{
private readonly S _s = new([1]);
public S GetS() => _s;
} |
Beta Was this translation helpful? Give feedback.
-
I agree with the idea but not with the example. With the given |
Beta Was this translation helpful? Give feedback.
-
This is exactly what is needed in order to simulate named indexes without a class allocation (#471 (reply in thread)): If the wrapper struct (or even just the indexer's set accessor) is readonly, you're definitely not in need of a followup assignment back to the property: var c = new C();
// Proposal: no error because the indexer's set accessor is readonly.
c.SimulatedNamedIndexer[42] = new object();
class C
{
public WrapperStruct SimulatedNamedIndexer => new(this);
public readonly struct WrapperStruct(C c)
{
public object this[int index]
{
// Indexer accesses private state or calls private methods in 'c'
get => ...;
set => ...;
}
}
} |
Beta Was this translation helpful? Give feedback.
-
Similarly, this is blocked but it would not have to be blocked; the blocking could be done on a per-nested-member basis, allowing members marked as // ❌ CS1918 Members of property 'C.ArraySegmentProp' of type 'ArraySegment<object>' cannot be assigned with an object
// initializer because it is of a value type
_ = new C { ArraySegmentProp = { [42] = new object() } };
// ~~~~~~~~~~~~~~~~
class C
{
public ArraySegment<object> ArraySegmentProp { get; set; }
} Unsure of the usefulness of making this more granular, though. |
Beta Was this translation helpful? Give feedback.
-
This is true, the compiler does behave as you describe, but the v7 standard does not account for this. I opened dotnet/csharpstandard#1277 to fix it and potentially discover whether it is a compiler bug that the restriction was lifted for invocation expressions (e.g. methods), but not for other expressions (e.g. simple member access expressions, e.g. properties). |
Beta Was this translation helpful? Give feedback.
-
I believe this discussion is a duplicate of #2068. At the time, it covered methods as well as properties, but the properties discussion remains. |
Beta Was this translation helpful? Give feedback.
-
Proposal to lift this restriction: #9174 |
Beta Was this translation helpful? Give feedback.
-
At the moment, the language considers calling the setter of any property on a value type to be "modifying" the value, and thus disallows it when the value is returned by a method (including a property getter):
There is a good rationale for this restriction, as any modification done to the fields of the value type is not observed, and thus the code is likely a mistake (even though this could be a warning in my opinion since the setter could do much more than setting a field).
However, there are certain value types whose (settable) properties do not modify the state of the value type, such as
ArraySegment
(simplified):This means there is a certain lack of parity when accessing arrays through
ArraySegment
, as opposed to aSpan
for example which has aref T this[int]
indexer:It seems this restriction has already been lifted once in regards to
readonly
value types, and that is if the value type is returned from a method (dotnet/csharpstandard#1277). I am proposing this to be allowed for all cases where a value type is returned, properties and methods alike, with likely the same reasoning as why it works for methods:struct
) isreadonly
however, it provably cannot assign any fields of the value. This means that, for the setter to be sensible, it must have a side effect or indirect effect on an external value not contained directly within the value type (such as through a reference).ref
was returned, and the returned value type likely acts only as a wrapper of such a reference. It is possible the method returns a "copy" where any modifications would remain unobserved anyway, but that is possible for reference types too, and they are allowed in that situation.In the example with
MyClass
, changingStorageSegment
to a method "fixes" the error, but I don't feel it should be necessary there ‒ using a method miscommunicates the intention of having a "stable" property holding the actual value, while a method should be used when some sort of copying might happen. To meobj.GetStorageSegment()[0] = 10;
looks much more "wrong" thanobj.StorageSegment[0] = 10;
which is natural in that situation and would work equally well.Beta Was this translation helpful? Give feedback.
All reactions