Struct vs Class

  • Thread starter Thread starter JohnGoogle
  • Start date Start date
One approach I've rather liked: provide an immutable interface to a
mutable object, and in cases where you'd like it to be immutable (like
in collections) use the interface. So you have the immutable IPoint
(which your collections handle) and the mutable Point (which you can
play with).

An alternate approach that would take some codegen but would make
immutables more palatable: autogenerate a constructor for the Struct
class that has one argument for each public property/field. Then,
autogenerate a "Copy" method for each such property, which uses that
constructor. So, for a 2D point we have the autogenerated constructor
public Point(x, y)
{
this.x = x;
this.y = y;
}

and the autogenerated Copy methods
public Point CopyWithX(x) {return new Point(x, this.y);}
public Point CopyWithY(y) {return new Point(this.x, y);}

So then you'd have a little convenience for doing changes on
immutables.

In general, constructors are just too damn much boilerplate legwork -
they're the sort of thing that screams out for codegen. The whole
DotNet framwork seems designed to work best with the basic,
parameterless constructor (witness the XmlSerializer, generics only
allowing new(), etc.) which is totally incompatible with immutable
objects. Now we're getting the object-initializers in 3.0 to make
initializing objects outside of the constructor even easier... I'm
starting to think that somebody at Microsoft hates parametric
constructors... but constructors are crucial to immutable objects. I'm
thinking the former, interface-based approach (or outright copying the
mutable object into a different, immutable type of object before using
it) would be most compatible with DotNet's preference for parameterless
constructors.

Dave said:
Hi Bruce,

I agree with your new reasoning :)

I guess I don't mind this too much:

Point pt = new Point(
[complex math for x goes here],
[complex math for y goes here]
);

I've used the format above a lot and it's just as clean as assignments that
follow construction, IMO. However, ".X = ..." and ".Y = ..." is a bit more
intuitive, although Point is probably a bad example of this because everyone
knows it's (x,y), but if it were a struct with several parameters then inline
construction might get confusing. Although, you can declare local variables
instead and pass the variables to the constructor. But that seems a bit
ridiculous just to construct a single value type.

Whatever - I'm just going to stick with the idea that all structs should be
immutable for now in light of the reasons that you have stated.
myRectangle.Location.X = 7;

I know this won't compile, but I think the only way for the compiler to miss
an assignment to a mutable struct that doesn't have a variable is when it's
boxed. I don't know what kind of effect this would have on the CLR, but maybe
a subsequent version of the framework could force immutability on all boxed
value-types and throw an exception on an assignment attempt. C# 8.5 perhaps?

Of course, I really don't know for sure if the compiler will miss non-variable
assignments on boxed structs only.

--
Dave Sexton

Bruce Wood said:
LOL... well, I guess appealing to the newbie thing wasn't a good
argument. :-)

What I meant to say is that if Location is a property of type Point,
then this looks as though it ought to work:

myRectangle.Location.X = 7;

but it doesn't. It's perfectly logical that it doesn't work, but the
logic is subtle and requires considerable knowledge of how .NET / C#
works. I dislike things that look as though they should do something
but which, for subtle reasons, do something else (or, in this case,
nothing). Granted, the compiler complains about this specific case, but
there are other cases in which it doesn't. I prefer that my code be
clearly readable to all: newbies and veterans alike, so I prefer to
give up syntactic sugar if it makes my code clearer.

It's so much easier to remember that you can't change a struct's
state--ever--than to remember that you can change it under some
circumstances but not under other circumstances, for perfectly logical
but non-obvious reasons. That just makes the code more difficult to
maintain, IMHO.

One of the clues that this is going on is when newbies (who usually
know other languages, so they're not new to programming, just new to
C#) make the same mistake over and over again. Now, sometimes the
feature is so truly useful that its very utility outweighs the
resulting confusion. Structs, for example, confuse the heck out of
people coming from the C/C++ world, but it's so very useful to be able
to create new objects with value semantics that I wouldn't give it up
to make the language easier to understand. Mere semantic sugar, such as

myPoint.X = 7;

is another thing entirely. I would rather live with more long-winded
code and do away with the confusion than have a handy shorthand that
then creates problems elsewhere in the language.

I've worked in several shops of mixed-language, mixed-skill
programmers, so I prefer code that someone not terribly familiar with
the language can understand. Mutable structs just throw a big wrench
into that, so I avoid them.
 
Dave said:
Hi Bruce,

I agree with your new reasoning :)

I guess I don't mind this too much:

Point pt = new Point(
[complex math for x goes here],
[complex math for y goes here]
);

I've used the format above a lot and it's just as clean as assignments that
follow construction, IMO. However, ".X = ..." and ".Y = ..." is a bit more
intuitive, although Point is probably a bad example of this because everyone
knows it's (x,y), but if it were a struct with several parameters then inline
construction might get confusing. Although, you can declare local variables
instead and pass the variables to the constructor. But that seems a bit
ridiculous just to construct a single value type.

Whatever - I'm just going to stick with the idea that all structs should be
immutable for now in light of the reasons that you have stated.
myRectangle.Location.X = 7;

I know this won't compile, but I think the only way for the compiler to miss
an assignment to a mutable struct that doesn't have a variable is when it's
boxed. I don't know what kind of effect this would have on the CLR, but maybe
a subsequent version of the framework could force immutability on all boxed
value-types and throw an exception on an assignment attempt. C# 8.5 perhaps?

The problem isn't really that boxed value types must be immutable. It's
better to say that temporary values that are never copied to any
addressable location must be immutable. That's what's going on in the
myRectangle.Location.X case: the .X being set is not the one held
against the rectangle, but the return result from a property, which is
a temporary value on the stack that will be thrown away as soon as the
operation is done. The compiler detects that the value will never be
used again, and complains that modifying it will have no effect.

Unfortunately, the compiler doesn't do this in every circumstance. In
particular, as you pointed, out, it seems not to be able to tell in the
case of boxed value types, in which case the unboxed value is a copy of
the value in the box, and so modifying the copy does not, of course,
modify the value in the box.

Even generics doesn't solve this latter problem, because unless I miss
my guess the following:

List<Point> myList = new List<Point>();
myList.Add(new Point(5,5));
myList[0].X = 7;

will not work either. I hope that the compiler is smart enough to catch
this and complain about it, though. (I'm not running 2.0 yet... anyone
care to confirm?)
 
Hi Martin,
One approach I've rather liked: provide an immutable interface to a
mutable object, and in cases where you'd like it to be immutable (like
in collections) use the interface. So you have the immutable IPoint
(which your collections handle) and the mutable Point (which you can
play with).

"Shield" pattern - I like it.
An alternate approach that would take some codegen but would make
immutables more palatable: autogenerate a constructor for the Struct
class that has one argument for each public property/field. Then,
autogenerate a "Copy" method for each such property, which uses that
constructor. So, for a 2D point we have the autogenerated constructor
public Point(x, y)
{
this.x = x;
this.y = y;
}

and the autogenerated Copy methods
public Point CopyWithX(x) {return new Point(x, this.y);}
public Point CopyWithY(y) {return new Point(this.x, y);}

So then you'd have a little convenience for doing changes on
immutables.

A bit convoluted and impractical - I'd rather just use a mutable struct with
good documentation :)
In general, constructors are just too damn much boilerplate legwork -
they're the sort of thing that screams out for codegen. The whole
DotNet framwork seems designed to work best with the basic,
parameterless constructor (witness the XmlSerializer, generics only
allowing new(), etc.) which is totally incompatible with immutable
objects. Now we're getting the object-initializers in 3.0 to make
initializing objects outside of the constructor even easier... I'm
starting to think that somebody at Microsoft hates parametric
constructors... but constructors are crucial to immutable objects. I'm
thinking the former, interface-based approach (or outright copying the
mutable object into a different, immutable type of object before using
it) would be most compatible with DotNet's preference for parameterless
constructors.

I don't see any preference for parameterless constructors aside from the
generic where : new() and Control/Component designer support. And don't
forget you always have the factory pattern when constructors become a problem.

Object initializers, BTW, are required for LINQ - I don't think they are meant
to supplant any constructor design issues.
 
Hi Bruce,
The problem isn't really that boxed value types must be immutable. It's
better to say that temporary values that are never copied to any
addressable location must be immutable. That's what's going on in the
myRectangle.Location.X case: the .X being set is not the one held
against the rectangle, but the return result from a property, which is
a temporary value on the stack that will be thrown away as soon as the
operation is done. The compiler detects that the value will never be
used again, and complains that modifying it will have no effect.

Unfortunately, the compiler doesn't do this in every circumstance. In
particular, as you pointed, out, it seems not to be able to tell in the
case of boxed value types, in which case the unboxed value is a copy of
the value in the box, and so modifying the copy does not, of course,
modify the value in the box.

That's exactly my point. The compiler catches it because it's detecting the
value type - but if it were boxed, then the compiler wouldn't complain. My
proposal is that the CLR should throw an exception at runtime when any public
property on a struct that only exists on the stack, and is currently boxed, is
assigned a value. Instead, mabye the CLR should protect all private fields
while the instance is boxed, on the stack, so that even method calls that
attempt to mutate the struct would fail as well. What do you think?
Even generics doesn't solve this latter problem, because unless I miss
my guess the following:

List<Point> myList = new List<Point>();
myList.Add(new Point(5,5));
myList[0].X = 7;

will not work either. I hope that the compiler is smart enough to catch
this and complain about it, though. (I'm not running 2.0 yet... anyone
care to confirm?)

Good idea - but the compiler is really acute:

{ Error 2 Cannot modify the return value of
'System.Collections.Generic.List<System.Drawing.Point>.this[int]' because it
is not a variable [file] 19 3 [project] }

Looks like my proposal might still have some merit :)
 
Dave said:
Hi Bruce,

My proposal is that the CLR should throw an exception at runtime when any public
property on a struct that only exists on the stack, and is currently boxed, is
assigned a value. Instead, mabye the CLR should protect all private fields
while the instance is boxed, on the stack, so that even method calls that
attempt to mutate the struct would fail as well. What do you think?

Well, the difficulty is that the value isn't "boxed on the stack". The
value is taken out of the box and then placed on the stack... in
effect, it's unboxed *onto* the stack. The compiler would then have to
remember that the value on the stack came from a box on the heap.

I would prefer that the compiler continue to be developed in the
direction it's going: disallow changes to structs based on where the
value is _going_, not where it came from. That is, you're not allowed
to modify a value on the stack if that modified value is not destined
for any permanent home, either stored into a variable on the stack,
stored into some object's state on the heap, or boxed back onto the
heap. Regardless of where it came from, modifying a value that has no
destination and is therefore thrown away doesn't make sense.

Unfortunately, there seem to be limits to the compiler's ability to
perform static analysis to find out if the modified value will end up
being used anywhere.
Even generics doesn't solve this latter problem, because unless I miss
my guess the following:

List<Point> myList = new List<Point>();
myList.Add(new Point(5,5));
myList[0].X = 7;

will not work either. I hope that the compiler is smart enough to catch
this and complain about it, though. (I'm not running 2.0 yet... anyone
care to confirm?)

Good idea - but the compiler is really acute:

{ Error 2 Cannot modify the return value of
'System.Collections.Generic.List<System.Drawing.Point>.this[int]' because it
is not a variable [file] 19 3 [project] }

I expected as much. It's unfortunate, though, that the message isn't
clearer: something like, "Setting a property of this
System.Drawing.Point value will have no effect because the modification
would be made to a temporary copy returned by the property this[int],
not to the backing value itself." Maybe it's a good thing I don't work
for MS, or all of the error messages would turn into novels. :-)
 
The problem is, of course, that structs can hold references to objects,
and struct code can access other classes. As such, assigning to a
struct's property and dropping it on the floor is not necessarily
fruitless. If you could be sure that a method/setter on a struct was
"functional" (that is, it only affects the struct itself and does not
use any other classes) then you can be assured the operation is
harmless.

However, you probably could safely apply this to fields. If our struct
has a public field value changed and nothing is being done with the
struct after that change (including copying it) then that field change
was 100% pointless.

Bruce said:
Dave said:
Hi Bruce,

My proposal is that the CLR should throw an exception at runtime when any public
property on a struct that only exists on the stack, and is currently boxed, is
assigned a value. Instead, mabye the CLR should protect all private fields
while the instance is boxed, on the stack, so that even method calls that
attempt to mutate the struct would fail as well. What do you think?

Well, the difficulty is that the value isn't "boxed on the stack". The
value is taken out of the box and then placed on the stack... in
effect, it's unboxed *onto* the stack. The compiler would then have to
remember that the value on the stack came from a box on the heap.

I would prefer that the compiler continue to be developed in the
direction it's going: disallow changes to structs based on where the
value is _going_, not where it came from. That is, you're not allowed
to modify a value on the stack if that modified value is not destined
for any permanent home, either stored into a variable on the stack,
stored into some object's state on the heap, or boxed back onto the
heap. Regardless of where it came from, modifying a value that has no
destination and is therefore thrown away doesn't make sense.

Unfortunately, there seem to be limits to the compiler's ability to
perform static analysis to find out if the modified value will end up
being used anywhere.
Even generics doesn't solve this latter problem, because unless I miss
my guess the following:

List<Point> myList = new List<Point>();
myList.Add(new Point(5,5));
myList[0].X = 7;

will not work either. I hope that the compiler is smart enough to catch
this and complain about it, though. (I'm not running 2.0 yet... anyone
care to confirm?)

Good idea - but the compiler is really acute:

{ Error 2 Cannot modify the return value of
'System.Collections.Generic.List<System.Drawing.Point>.this[int]' because it
is not a variable [file] 19 3 [project] }

I expected as much. It's unfortunate, though, that the message isn't
clearer: something like, "Setting a property of this
System.Drawing.Point value will have no effect because the modification
would be made to a temporary copy returned by the property this[int],
not to the backing value itself." Maybe it's a good thing I don't work
for MS, or all of the error messages would turn into novels. :-)
 
Martin said:
The problem is, of course, that structs can hold references to objects,
and struct code can access other classes. As such, assigning to a
struct's property and dropping it on the floor is not necessarily
fruitless. If you could be sure that a method/setter on a struct was
"functional" (that is, it only affects the struct itself and does not
use any other classes) then you can be assured the operation is
harmless.

However, you probably could safely apply this to fields. If our struct
has a public field value changed and nothing is being done with the
struct after that change (including copying it) then that field change
was 100% pointless.

The compiler seems able to sort this out in some situations, such as
that of Point. X and Y are properties, not fields, and the compiler
seems able to determine that they have no side-effects and therefore
setting them in a temporary copy is a useless operation. I assume that
this is part-and-parcel of the information the the compiler leaves
lying around in assemblies to allow the JITter to decide whether it can
in-line property assignments, etc.
 
Hi Bruce,
Well, the difficulty is that the value isn't "boxed on the stack". The
value is taken out of the box and then placed on the stack... in
effect, it's unboxed *onto* the stack.

Thanks for pointing that out. I shouldn't have wrote "boxed on the stack"
because that's not exactly what I meant :)

Lol - I just tried to illustrate my idea in code, but to my surprise the
compiler caught it! Clever:

struct Value
{
public object NestedValue;
public int Number;
}

static void Main()
{
Value value = new Value();
value.NestedValue = new Value();

((Value) value.NestedValue).Number = 1;

Console.WriteLine(((Value) value.NestedValue).Number);
}

Error 1 Cannot modify the result of an unboxing conversion [file] 45 5
[project]


I thought this would be the simplest example of what I was trying to explain.
I'd really like to know if there are any cases where unaddressed
value-assignment won't be caught by the 2.0 compiler. Maybe we're making too
many assumptions now :)

(Is unaddressed an appropriate term to describe a struct that has no
established roots in memory?)

The compiler would then have to
remember that the value on the stack came from a box on the heap.

Good point and I assumed that the CLR could do that, but maybe it's just not
feasible.
I would prefer that the compiler continue to be developed in the
direction it's going: disallow changes to structs based on where the
value is _going_, not where it came from. That is, you're not allowed
to modify a value on the stack if that modified value is not destined
for any permanent home, either stored into a variable on the stack,
stored into some object's state on the heap, or boxed back onto the
heap. Regardless of where it came from, modifying a value that has no
destination and is therefore thrown away doesn't make sense.

Unfortunately, there seem to be limits to the compiler's ability to
perform static analysis to find out if the modified value will end up
being used anywhere.

I'm not so sure that there are any limits. Maybe at a certain level of
abstraction the compiler finally gives up?
{ Error 2 Cannot modify the return value of
'System.Collections.Generic.List<System.Drawing.Point>.this[int]' because
it
is not a variable [file] 19 3 [project] }

I expected as much. It's unfortunate, though, that the message isn't
clearer: something like, "Setting a property of this
System.Drawing.Point value will have no effect because the modification
would be made to a temporary copy returned by the property this[int],
not to the backing value itself." Maybe it's a good thing I don't work
for MS, or all of the error messages would turn into novels. :-)

That could be a good thing, Bruce. People who don't know what the error means
would benefit from an in-depth message and people who do know what it means
can just avoid it to begin with ;)
 
Hi Martin,

I'm partial to the readonly private field idea myself, however I'm
hard-pressed to think of an example where the 2.0 compiler will miss the
assignment.

Originally, I thought that the compiler would only miss an assignment if it
didn't know whether the struct being mutated was retrieved from a boxed
struct, and therefore wouldn't have any roots in memory. But the compiler
knew this.

Actually, I just realized that the example I posted to Bruce was overkill.
The following code produces the same results:

object o = Point.Empty;
((Point) o).X = 5;

So it seems that you really can't trick the compiler in this fashion.

Does anybody have any sample code that will trick the compiler to allow
assignment to a struct without roots in memory?

It seems like mutable structs might not be so dangerous after all (< more
assumptions) ;)

--
Dave Sexton

Martin Z said:
The problem is, of course, that structs can hold references to objects,
and struct code can access other classes. As such, assigning to a
struct's property and dropping it on the floor is not necessarily
fruitless. If you could be sure that a method/setter on a struct was
"functional" (that is, it only affects the struct itself and does not
use any other classes) then you can be assured the operation is
harmless.

However, you probably could safely apply this to fields. If our struct
has a public field value changed and nothing is being done with the
struct after that change (including copying it) then that field change
was 100% pointless.

Bruce said:
Dave said:
Hi Bruce,

My proposal is that the CLR should throw an exception at runtime when any
public
property on a struct that only exists on the stack, and is currently
boxed, is
assigned a value. Instead, mabye the CLR should protect all private
fields
while the instance is boxed, on the stack, so that even method calls that
attempt to mutate the struct would fail as well. What do you think?

Well, the difficulty is that the value isn't "boxed on the stack". The
value is taken out of the box and then placed on the stack... in
effect, it's unboxed *onto* the stack. The compiler would then have to
remember that the value on the stack came from a box on the heap.

I would prefer that the compiler continue to be developed in the
direction it's going: disallow changes to structs based on where the
value is _going_, not where it came from. That is, you're not allowed
to modify a value on the stack if that modified value is not destined
for any permanent home, either stored into a variable on the stack,
stored into some object's state on the heap, or boxed back onto the
heap. Regardless of where it came from, modifying a value that has no
destination and is therefore thrown away doesn't make sense.

Unfortunately, there seem to be limits to the compiler's ability to
perform static analysis to find out if the modified value will end up
being used anywhere.
Even generics doesn't solve this latter problem, because unless I miss
my guess the following:

List<Point> myList = new List<Point>();
myList.Add(new Point(5,5));
myList[0].X = 7;

will not work either. I hope that the compiler is smart enough to catch
this and complain about it, though. (I'm not running 2.0 yet... anyone
care to confirm?)

Good idea - but the compiler is really acute:

{ Error 2 Cannot modify the return value of
'System.Collections.Generic.List<System.Drawing.Point>.this[int]' because
it
is not a variable [file] 19 3 [project] }

I expected as much. It's unfortunate, though, that the message isn't
clearer: something like, "Setting a property of this
System.Drawing.Point value will have no effect because the modification
would be made to a temporary copy returned by the property this[int],
not to the backing value itself." Maybe it's a good thing I don't work
for MS, or all of the error messages would turn into novels. :-)
 
Hi Bruce,

This is the only relevant information I found in the spec cited afterwards:

"When a property or indexer declared in a struct-type is the target of an
assignment, the instance expression associated with the property or indexer
access must be classified as a variable. If the instance expression is
classified as a value, a compile-time error occurs. [Note: Because of §14.5.4,
the same rule also applies to fields as well. end note]"
[§14.3.1 Simple Assignment]
Draft C# Language Spec March 2001
http://msdn.microsoft.com/net/ecma/WD05-Review.pdf

The following spec seems to contain no information that is relevant to our
discussion:

C# 2.0 Language Spec
http://download.microsoft.com/downl...-48fe-9e5e-f87a44af3db9/SpecificationVer2.doc

So it seems that the C# compiler prevents assignment to value-types that
aren't classified as a variable. [§14.3.1 Simple Assignment]

I think that covers everything, unless you can somehow set a private field of
a struct while it's boxed, which is exactly what I suggested that the CLR
should prevent.

Was the immutable structure paradigm established by older languages and just
carried over by experts as something to warn people about, without any hard
evidence of it being problematic in C#?

I could have sworn that I've seen examples of C# code that was susceptible to
the behavior we have been discussing without prevention by the compiler,
although I can almost remember all of them being tagged with what you said
before: "Granted, the compiler complains about this specific case, but there
are other cases in which it doesn't". With all due respect, are you sure?

I like having the flexibility of "Point.X =", but I've always been against it
because of the aforementioned reasons, which I'm starting to doubt. Until I
dig up some more proof that structs shouldn't be mutable I'm going to consider
this issue unresolved, in light of this discussion, and try to avoid it when
people ask me why this should be. I'll just change the subject: "So, how
about them Yanks?" ;)
 
Thanks for all the replies in this thread. I've learnt a lot (I
think!).

I'm going to investigate the nullable types mentioned by Ryan.

John.
 
I'm now confused by earlier comments that said that a struct should be
immutable.

I've been reading the C# Language Specification for Structs and found
the following in $11.3.1 Value semantics:

Given the declaration

struct Point
{
public int x, y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
}


the code fragment

Point a = new Point(10, 10);
Point b = a;
a.x = 100;
System.Console.WriteLine(b.x);

outputs the value 10. The assignment of a to b creates a copy of the
value, and b is thus unaffected by the assignment to a.x. Had Point
instead been declared as a class, the output would be 100 because a and
b would reference the same object

I understand why the output value is 10.

My concern is that various people have said that a struct should be
immutable and Microsoft says so in their documentation. If that is the
case, why does the example in the language specification provide public
fields (X,Y) and show an example where they are modified?

John.
 
I'm now confused by earlier comments that said that a struct should be
immutable.

I've been reading the C# Language Specification for Structs and found
the following in $11.3.1 Value semantics:

Given the declaration

struct Point
{
public int x, y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
}


the code fragment

Point a = new Point(10, 10);
Point b = a;
a.x = 100;
System.Console.WriteLine(b.x);

outputs the value 10. The assignment of a to b creates a copy of the
value, and b is thus unaffected by the assignment to a.x. Had Point
instead been declared as a class, the output would be 100 because a and
b would reference the same object

I understand why the output value is 10.

My concern is that various people have said that a struct should be
immutable and Microsoft says so in their documentation. If that is the
case, why does the example in the language specification provide public
fields (X,Y) and show an example where they are modified?

Because Microsoft doesn't always provide the best of examples. :-)

Really, because the statement "structs should be immutable" is a
guideline, not a law. For example, System.Drawing.Point and
System.Drawing.Rectangle are mutable structs.

The problem with mutable structs is that the disadvantages typically
outweight the advantages. In some cases, the people at Microsoft
decided that the advantages won out. It's a matter of taste (I don't
agree with them in the cases of Point and Rectangle), but that was
their decision.

The advantage of mutable structs is that sometimes it makes the code
syntactically cleaner. So, using the example of System.Drawing.Point,
it reads better to write

Point a = new Point(5, 10);
....
a.X = 15;

than it reads to write

Point a = new Point(5, 10);
....
a = new Point(15, a.Y);

The latter looks clumsy and roundabout, while the former is more
intuitive.

The disadvantage is that this intuitive, cleaner-looking code falls
absolutely flat on its face in many common situations, as you've seen
discussed in this thread. Fortunately, the compiler catches many of
them, but the whole thing causes much head-scratching and confusion.

Personally, I prefer to live with the latter, clumsy syntax and avoid
the problems that mutable structs can introduce. Evidently the folks at
MS don't take as extreme a view as I do. Their rules can be stated as,
"Structs should be immutable unless you can think of a good reason why
one shouldn't be." My rules can be stated as, "Structs should be
immutable, and I've never seen a good reason why one shouldn't be."
 
Hi John,

I assume that Microsoft used a mutable struct to try and show why you should
make structs immutable.
Point b = a;

If it's not obvious to a programmer that "b" is a copy of "a" then they might
make the wrong assumption about the values that "b" contains when "a" is
modified, as you've illustrated, but if "a" was immutable then that can't
happen.

The idea that "all structs should be immutable" is to prevent programmers from
inadvertently introducing bugs into their code by misusing value-types.

I tried to figure out in this thread whether the idea has any merit. It
seems, in my research thus far, that the C# compiler will prevent one form of
misuse whereby a value is assigned to a struct that doesn't have a variable
backing it, in which case the value would be lost and so the operation would
be pointless. This can occur when you try to assign a value to an instance of
a struct by setting one of its properties or fields after it has just been
unboxed or if it was obtained via a public property, but not assigned to a
variable in both cases.

I've heard experts state that this could be a problem in C# but never saw any
evidence of that. Though the compiler does allow structs to be mutated in
method calls, even when not assigned to a variable.

I don't think I, or anyone I know has ever introduced a bug into their code
because they called a method on a struct that wasn't assigned to a variable.
Therefore, I'm not so sure that is a good excuse for universal struct
immutability.

The only remaining excuse that I'm aware of for the axiom is the idea that
value-type semantics might not be intuitive enough for us programmers to have
the ability in the first place. I dislike that, and I think that's what
Microsoft's example is trying to illustrate.

What do you think?
 
Dave said:
Hi John,

I assume that Microsoft used a mutable struct to try and show why you should
make structs immutable.


If it's not obvious to a programmer that "b" is a copy of "a" then they might
make the wrong assumption about the values that "b" contains when "a" is
modified, as you've illustrated, but if "a" was immutable then that can't
happen.

The idea that "all structs should be immutable" is to prevent programmers from
inadvertently introducing bugs into their code by misusing value-types.

I tried to figure out in this thread whether the idea has any merit. It
seems, in my research thus far, that the C# compiler will prevent one form of
misuse whereby a value is assigned to a struct that doesn't have a variable
backing it, in which case the value would be lost and so the operation would
be pointless. This can occur when you try to assign a value to an instance of
a struct by setting one of its properties or fields after it has just been
unboxed or if it was obtained via a public property, but not assigned to a
variable in both cases.

I've heard experts state that this could be a problem in C# but never saw any
evidence of that. Though the compiler does allow structs to be mutated in
method calls, even when not assigned to a variable.

Remember, however, that the compiler catches only the obvious. I don't
believe that the compiler could catch this:

public struct ValueThingy
{
int _x;
int _y;

public ValueThingy(int x, int y) { this._x = x; this._y = y; }
public void Flip() { int flip = this._x; this._x = this._y; this._y
= flip; }
}

public class Foo
{
ValueThingy _v;

public Foo() { this._v = new ValueThingy(1, 2); }

public ValueThingy V { get { return this._v; } }
}

....

Foo f = new Foo();
f.V.Flip();

which will, of course, modify a temporary copy of the ValueThingy
returned by the property V, and then throw away the result.

Once you start getting into structs that can be altered via methods,
it's a whole other ballgame, and the compiler can't help you. The
compiler helps only in the classic case of modification via property
setters.
 
Hi Bruce,

Once you start getting into structs that can be altered via methods,
it's a whole other ballgame, and the compiler can't help you. The
compiler helps only in the classic case of modification via property
setters.

Yes, I pointed that out in my article too.

The compiler prevents setting fields as well as properties, so methods are the
only variable here.

Everyone always makes it seem like there are so many unintuitive ways you can
screw up using a mutable struct, but calling methods on value-types that
aren't assigned to a variable appears to be the only way and that happens
rarely (never in my experience). Therefore, I don't agree this constitutes
that when designing a struct there should be a default of immutability in the
design unless mutability is required anymore than that would be true when
designing a class. Also, not allowing mutability of value-types in the
language at all seems quite extreme to me as well.

I used to side with you, but I've changed gears because our discussion has
showed me that the compiler does handle what I believe to be the worst cases
of unintuativeness when using structs and that any other restrictions would
simply be catering to the needs of programmers that don't really understand
value-types in the first place.
 
BTW, how great is the word, "unintuitiveness"?

I doubt it's real but I couldn't think of anything better to use at the time
;)
 
Hi Bruce,

I just realized too that the following
Therefore, I don't agree this constitutes that when designing a struct there
should be a default of immutability in the design unless mutability is
required anymore than that would be true when designing a class

might give the wrong impression.

I was trying to say that I think classes should be designed with immutability
in mind, by default, and that the evidence provided does not indicate to me
there is any more of a reason to do the same when designing structs than the
reasons for doing so when designing a class.

I promise that this is my last self-post :)

Thanks for the conversation, BTW.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top