M
Martin Z
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.
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.