6

Can someone tell me why the commented line of code (one before last) does not compile? Isn't it the same as the line following it?

public struct OtherStruct
{
    public int PublicProperty { get; set; }
    public int PublicField;

    public OtherStruct(int propertyValue, int fieldValue)
        : this()
    {
        PublicProperty = propertyValue;
        PublicField = fieldValue;
    }

    public int GetProperty()
    {
        return PublicProperty;
    }
    public void SetProperty(int value)
    {
        PublicProperty = value;
    }
}

public struct SomeStruct
{
    public OtherStruct OtherStruct { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        SomeStruct a = new SomeStruct();
        //a.OtherStruct.PublicProperty++;
        a.OtherStruct.SetProperty(a.OtherStruct.GetProperty() + 1);
    }
}
0

3 Answers 3

10

SomeStruct.OtherStruct is a property, returning a value - it's not a variable. This line:

a.OtherStruct.PublicProperty++;

is like calling:

a.get_OtherStruct().PublicProperty++;

Because the expression a.get_OtherStruct() is a value rather than a variable, it's a bit like doing this:

OtherStruct tmp = a.get_OtherStruct();
tmp.PublicProperty++;

Changing the value of PublicProperty in the copy of OtherStruct returned by the property isn't going to change the original value at all. That's almost certainly not your intention. The C# designers foresaw this sort of problem, and managed to prohibit it in many situations.

Note that if OtherStruct were a reference type (a class) instead, then it would be the reference that was copied, not the values within it... so changing tmp.PublicProperty would make a difference. See my article on reference and value types for more information.

Btw, mutable structs like this are generally a really bad idea. They cause all kinds of problems, and hard-to-predict code.

EDIT: In response to your "answer", the two lines aren't the same: the a.OtherStruct property expression isn't the target of an assignment operator or a compound assignment operator.

You can argue that you'd like C# to be defined in a way which would allow this (although I'd still disagree) but the compiler is implementing the specification correctly. See section 10.7.2 of the C# 3.0 spec for more details.

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

5 Comments

I think it may be clearer if you used an example that showed the difference between value types and reference types.
@Jeff: I've added a paragraph and a link... hopefully that will do the trick.
Yup, I think that makes a big difference. It's a good explanation, btw, just think subject can be confusing so every little helps. Cheers.
Please see the next answer - I could not fit it into a comment.
I see what you're saying. In the OtherStruct example, a.OtherStruct gets laid on the stack, which yields a copy. While in the SomeStruct example, the set of the PublicProperty is going directly to the variable and not to a copy. That makes sense, thank you.
1

Apologies for not using a comment, I don't think it will fit. Jon, this is not an actual implementation, I'm just trying to get a deeper understanding of structs, so no worries about me implementing mutable structs :)

Anyway, I'm not sure you are correct. Consider this code, it's almost the same as first example:

public struct SomeStruct
{
    public int PublicProperty { get; set; }
    public int PublicField;

    public SomeStruct(int propertyValue, int fieldValue)
        : this()
    {
        PublicProperty = propertyValue;
        PublicField = fieldValue;
    }

    public int GetProperty()
    {
        return PublicProperty;
    }
    public void SetProperty(int value)
    {
        PublicProperty = value;
    }
}

class Program
{
    static void Main(string[] args)
    {
        SomeStruct a = new SomeStruct(1, 1);
        a.PublicProperty++;
        a.SetProperty(a.GetProperty()+1);
    }
}

Now, looking at the msil using ildasm, gives me the following for the Main method:

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

// Code size       45 (0x2d)

.maxstack  3

.locals init ([0] valuetype ConsoleApplication1.SomeStruct a)

IL_0000:  nop

IL_0001:  ldloca.s   a

IL_0003:  ldc.i4.1

IL_0004:  ldc.i4.1

IL_0005:  call       instance void ConsoleApplication1.SomeStruct::.ctor(int32,
                                                                         int32)
IL_000a:  nop

IL_000b:  ldloca.s   a

IL_000d:  dup

IL_000e:  call       instance int32 

ConsoleApplication1.SomeStruct::get_PublicProperty()

IL_0013:  ldc.i4.1

IL_0014:  add

IL_0015:  call       instance void 

ConsoleApplication1.SomeStruct::set_PublicProperty(int32)

IL_001a:  nop

IL_001b:  ldloca.s   a

IL_001d:  ldloca.s   a

IL_001f:  call       instance int32 ConsoleApplication1.SomeStruct::GetProperty()

IL_0024:  ldc.i4.1

IL_0025:  add

IL_0026:  call       instance void ConsoleApplication1.SomeStruct::SetProperty(int32)

IL_002b:  nop

IL_002c:  ret

}

I apologize for the terrible formatting, I'm not sure how to make it look normal. Anyway, hopefully, you can see that the last 2 lines of code in the main method are actually identical.

Therefore, I would argue that, from the previous post, this line:

a.OtherStruct.PublicProperty++;

Is actually identical to the line after it:

 a.OtherStruct.SetProperty(a.OtherStruct.GetProperty() + 1);

And therefore it seems to me that the first line does not compile simply because the compiler does not support it, not because it is not legal.

What do you think?

2 Comments

This should have been edited into the question instead of added as an answer... anyway, I'll edit my answer appropriately.
OK, will remember for next time.
0

Structures should not expose field-like read-write properties absent a good reason for doing so. Instead, they should simply expose fields directly. Most of the "problems" that are attributed to "mutable structs" are really problems with structs that expose read-write properties. In your example, if you had simply made member OtherStruct of type SomeStruct be a field rather than a propertie (i.e. lose the { get; set; } then there would be no problems with the nested struct access.

Note also that the one "problem" people have with "mutable structs" which isn't associated with mutating this (something which field-like properties do) stems from fact that in the following code:

  someStructType myThing = MyDataSupplier.GetSomeData();
  myThing.someField = something;

the change to myThing won't be percolate back to MyDataSupplier absent some code like:

  MyCollection[whatever] = myThing;

My reaction would be "Naturally. Knowing that someStructType is a struct with field someField is sufficient to know that myThing.someField may be altered without affecting anything else in the universe." By contrast, if one were to substitute myClassType, and myClassType had a mutable member (whether field or property), the above code "might" cleanly alter the data in MyDataSupplier, or it might not, and such modification might or might not corrupt other aspects of the system state. Effectively, the complaint is that one would have predictable behavior which would not always match what one wants to do, as opposed to having semantics which depend upon many things, including in some cases other consumers of MyDataSupplier. Note also that if someStructType exposed a read-write property rather than a field, one would have to examine the code associated with that property to determine whether it might affect things outside the structure instance upon which it is invoked.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.