Remove CS 1612 for readonly structs #2068
Replies: 14 comments 3 replies
-
I didn't realize this was a language issue, does the spec forbid this currently? |
Beta Was this translation helpful? Give feedback.
-
I'm completely confused by this. According to all documentation around readonly structs, "The compiler checks that the struct is indeed immutable and consists only of readonly fields and/or readonly properties" (quote taken from The ‘in’-modifier and the readonly structs in C#). Yet the setter in the OP's code seems to be allowed and I checked this on sharplab and setters seem to be allowed in read-only structs, which surely makes a totally mockery of the "readonly" label? |
Beta Was this translation helpful? Give feedback.
-
@DavidArno |
Beta Was this translation helpful? Give feedback.
-
If it allows setters, then it is not even shallowly immutable. For it to be shallowly immutable to should force the developer to jump through hoops to mutate things at a deeper level. Allowing setters right there in the public API makes any immutable claims a complete farce. Sure people will always abuse language features, but I'm really shocked and disappointed that such abuse is so openly encouraged by allowing any setters of any form to exist in any indexers and properties of a readonly struct. |
Beta Was this translation helpful? Give feedback.
-
I get what you mean about setters, but if the setters cannot actually modify the underlying field(is there is one) is it actually an issue? I'm just abusing the indexer syntax here but the struct is doesn't ever change after construction. |
Beta Was this translation helpful? Give feedback.
-
I'd like to see this applied to CS1654 ("mutating" foreach iteration variables) and readonly struct members (#1710) as well. I ran into this limitation working on a wrapper for native code that stores data in several large arrays rather than one array of structs. We're using a thin view struct to make accessing this data more natural. (Much like @Ollhax is with his ECS.) The implementations generally look something like this: internal unsafe struct NativePolygonMesh
{
public readonly ushort* Flags;
public readonly byte* AreaIds;
public readonly int PolygonCount;
}
public unsafe class PolygonMesh : IEnumerable<Polygon>
{
internal readonly NativePolygonMesh* Native;
public int PolygonCount => Native->PolygonCount;
public Polygon this[int index]
{
get
{
if (index < 0 || index >= Native->PolygonCount)
{ throw new IndexOutOfRangeException(); }
return new Polygon(this, index);
}
}
// Omitted: IEnumerable and enumerator implementation.
}
public unsafe readonly struct Polygon
{
private readonly PolygonMesh mesh;
private readonly int index;
internal Polygon(PolygonMesh mesh, int index)
{
this.mesh = mesh;
this.index = index;
}
public byte AreaId => mesh.Native->AreaIds[index];
public ushort Flags
{
get => mesh.Native->Flags[index];
set => mesh.Native->Flags[index] = value;
}
} Ideally I'd like to access the data like in these examples, but CS1654/CS1612 prevent it: public void UpdateFlags(PolygonMesh polygonMesh)
{
foreach (Polygon polygon in polygonMesh)
{
polygon.Flags = 100; // error CS1654: Cannot modify members of 'polygon' because it is a 'foreach iteration variable'
}
}
public void UpdateFlags2(PolygonMesh polygonMesh)
{
for (int i = 0; i < polygonMesh.PolygonCount; i++)
{
polygonMesh[i].Flags = 100; // error CS1612: Cannot modify the return value of 'polygonMesh.this[int]' because it is not a variable
}
} We're currently using the workaround below, but it leaks the fact that foreach (Polygon _polygon in polygonMesh)
{
Polygon polygon = _polygon;
polygon.Flags = 100;
} |
Beta Was this translation helpful? Give feedback.
-
Any news? This would certainly improve usage of |
Beta Was this translation helpful? Give feedback.
-
Another example: // Some wrapper. It always just forward operations.
// No any mutable state stored, except subject identification.
public readonly struct A
{
private readonly object _objectStore;
private readonly int _objectIndex;
public int SomeProperty
{
readonly get => throw new System.NotImplementedException();
set => throw new System.NotImplementedException();
}
}
// Client's code (also a wrapper).
public readonly struct B // wraps A
{
private readonly A _target;
public readonly A Target => _target;
public readonly A GetTarget() => _target;
}
public class Sample
{
public void DoSomething()
{
var x = new B();
x.Target.SomeProperty = 1; // CS1612: ...
x.GetTarget().SomeProperty = 1; // That's compiled fine.
}
} So, why compiler ever generate error? Looks like CS1612 was designed to protect from struct hidden errors, but instead it protect from right code design, while can't protect about hidden copies generally... so, IMO, is bug/unwanted feature. |
Beta Was this translation helpful? Give feedback.
-
Not allowing setters in autoproperties makes sense, but in normal properties it makes zero sense since they don't necessarily (actually, they simply can't) mutate the readonly struct since fields are all readonly. |
Beta Was this translation helpful? Give feedback.
-
Linking dotnet/roslyn#45284 Seems like making |
Beta Was this translation helpful? Give feedback.
-
Proposal to lift this restriction: #9174 |
Beta Was this translation helpful? Give feedback.
-
See also discussion at #8364 |
Beta Was this translation helpful? Give feedback.
-
@jnm2 can we also remove CS1918 while we're at it? |
Beta Was this translation helpful? Give feedback.
-
https://github.com/dotnet/csharplang/pull/9233/files indicates how behavior of |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
@AlgorithmsAreCool commented on Tue Jun 05 2018
Version Used:
LangVersion =7.3
Steps to Reproduce:
Expected Behavior:
No Error
Actual Behavior:
Very Error
So since C# doesn't have Indexed Properties I like to sometimes use this flyweight accessor pattern to simulate it.
But C# doesn't allow this because of reasons @gafter explained in #22592
Specifically @gafter says that
I'm not clear on the how using a struct vs a class effects if something is considered an lvalue or a rvalue but now that we have readonly structs, can't the compiler see that modifying the struct is not possible and therefore whatever the setter is doing will be safe?
@Ollhax commented on Sat Oct 06 2018
Just want to add another case here. I'm working on a Entity Component System and I'm trying to hide the component data layout behind a readonly ref struct. Code:
Beta Was this translation helpful? Give feedback.
All reactions