3

In C#, I have a struct like this:

public struct Slab { public float[] sizeM; public string textureSrc; //more members, not relevant here... } 

And another like this:

public struct Tombstone { public Slab mainSlab; public Slab? basing; //more... } 

Now, I want to modify members of basing:

uiState[0].stone.basing.Value.sizeM[2] = Int32.Parse(breadthField.Value) / 100.0f; uiState[0].stone.basing.Value.textureSrc = fileName; 

(uiState[0].stone is of type Tombstone)

First of these two calls works correctly, as I'm just changing a member of the array in basing, not the array itself. However, the second complains:

Cannot modify the return value of 'Slab?.Value' because it is not a variable 

It works if I do the same to mainSlab which is not nullable. Is there a way to do this without copying the whole basing to a local variable for changes?

3 Answers 3

4

Is there a way to do this without copying the whole basing to a local variable for changes?

No, because Nullable<T> doesn't provide direct access to the underlying value field. You can't modify it "in place".

There are all kinds of little issues like this when you use mutable structs. I'd strongly advise you to use classes or immutable structs whenever possible, to avoid these corner cases.

Sign up to request clarification or add additional context in comments.

4 Comments

Probably I should, you're right. My rule of thumb is that use classes if you want polymorfism, structs otherwise, but it may be that rule is only good in D, not C#. I'll mark your answer as accepted later if you're right and someone doesn't come up with an idea.
@dukc: That's definitely not a good rule in C#. (Additionally, public fields are almost always a bad idea in C# as well, as you lose any chance of encapsulation.) See learn.microsoft.com/en-us/dotnet/standard/design-guidelines/… for more details about when to use a class and when to use a struct.
"My rule of thumb is that use classes if you want polymorfism, structs otherwise" - yeah, that's a terrible idea, and it will bite you; you'd be amazed how often people shoot themselves in the foot with this. Mutable structs are valid, but they are absolutely not synonymous with "lightweight classes, for when you don't need virtual methods"
FWIW, Nullable.GetValueRefOrDefaultRef (learn.microsoft.com/en-us/dotnet/api/…) introduced in .NET 7.0 does give you direct access to the underlying field. However it does return a readonly ref. You can in principle cast it to a read-write ref with Unsafe.AsRef, but you probably shouldn't.
3

Frankly, the main error here is almost certainly: having a mutable struct. Now, there are scenarios where mutable structs make sense, but those scenarios are narrow, and this almost certainly isn't one of them.

Frankly, your code will be much easier to rationalize if you stop doing that; with recent C#, you can even use readonly struct to help enforce this (and to get better behaviour with in):

public readonly struct Slab { public readonly float[] sizeM; public readonly string textureSrc; //more members, not relevant here... } 

(personally I'd also consider properties instead of public fields, but that is a separate issue)

Then it becomes obvious that you can only assign the entire object:

Slab? foo = ... ... some logic foo = new Slab(size, textureSource); // perhaps taking new values from the old 

The only other alternative is basically to do the same thing anyway:

Slab? foo = ... // ... var innerVal = foo.GetValueOrDefault(); // or .Value if you've already null-checked // ... innerVal.textureSrc = ... foo = innerVal; 

2 Comments

Fun fact: the reason why my code is uiState[0] is that uiState is a value type too, and I needed a pointer wannabe to it. Hey, I like value semantics by default! I could do the same for basing, but I think I'll copy the struct this time.
@dukc are you using recent versions of C# ? "ref locals" are a thing - you can do ref UIState x = ref uiState[0];, which makes x a managed reference (technically a managed pointer); if all the .whatever here are fields into structs, that can also be used - "interior pointers". Hard to tell without full code, but you can probably create a ref Slab that is an interior pointer to uiState[0].stone.basing ! You cannot, however, create an interior pointer to the Nullable<T>'s field, because it isn't exposed; but: ref var basing = ref uiState[0].stone.basing;
0

There may be many possible fixes for this "problem", depending on the rest of your design and requirements... For example:

public struct Tombstone { public Slab mainSlab; public Slab basing; public bool hasBasing => basing.sizeM != null; //more... } 

To be honest I never user Nullables... Nullable value types, what's next, global rvalues?

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.