Struct vs Class

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Dave Sexton

    #16
    Re: Struct vs Class

    Hi Marin,
    structs containing references to mutable objects is what I meant by
    "aliasing" - that is, if I have a struct I may assume that everything I
    do with it will be copy-semantics... however, if it contains a
    reference to a mutable class (say a stringbuilder) then if I make
    changes to that stringbuilder, then all copies (not just the object I'm
    accessing to make the changes) will see that, which is unexpected
    behaviour for a struct. That is, an alias where we expect a copy.
    Thanks for clearing that up. I didn't realize that you were agreeing with
    Bruce when you wrote, "aliasing".

    But I think DictionaryEntry proves that it may be appropriate to define a
    struct that has references to mutable objects. When DictionaryEntry contains
    references, the value of the DictionaryEntry itself is the relationship
    between the references. It doesn't matter whether the objects being
    referenced can be mutated or not. Therefore, structs that assign value to
    references or their relationships should have no problem referencing mutable
    objects.
    And yes, I agree that Nullables _should_ be valuetype, and likely are
    in implementation - I just thought that they were reference-types
    because errors thrown by the XmlSerializer informed me that they were
    reference types... which is likely just the serializer getting very
    confused by the concept of a nullable value-type.
    Nullable<Tis an immutable value type.
    That is a strange error - quite possibly a bug.

    --
    Dave Sexton

    "Martin Z" <martin.zarate@ gmail.comwrote in message
    news:1162235821 .307961.321930@ h48g2000cwc.goo glegroups.com.. .
    structs containing references to mutable objects is what I meant by
    "aliasing" - that is, if I have a struct I may assume that everything I
    do with it will be copy-semantics... however, if it contains a
    reference to a mutable class (say a stringbuilder) then if I make
    changes to that stringbuilder, then all copies (not just the object I'm
    accessing to make the changes) will see that, which is unexpected
    behaviour for a struct. That is, an alias where we expect a copy.
    >
    And yes, I agree that Nullables _should_ be valuetype, and likely are
    in implementation - I just thought that they were reference-types
    because errors thrown by the XmlSerializer informed me that they were
    reference types... which is likely just the serializer getting very
    confused by the concept of a nullable value-type.
    >
    Dave Sexton wrote:
    >Hi Martin,
    >>
    obviously if you're using structs because
    you want value-type semantics then it doesn't matter what you stuff in
    them (as long as they're immutable to avoid confusing aliasing
    behaviour).
    >>
    >What do you mean by "aliasing behaviour"?
    >>
    Funny that nullables are value-type - I recently got an error from the
    XmlSerializer where it complained that they were reference types, and
    thus couldn't be XmlAttributes. That was just silly.
    >>
    >I think Nullable<Tshoul d be a value type just like DictionaryEntry . They
    >each assign a value to the objects that they contain - nullability or hash
    >entry, respectively.
    >>
    >Although nullability is only useful for value types, which is why I suspect
    >that you can't make a Nullable<string >, for example.
    >>
    >But hash entries can be anything, including mutable objects.
    >DictionaryEntr y
    >is just a value: the relationship between a key and its corresponding
    >value.
    >If the key or the value are object references then the value of the hash
    >entry
    >itself is simply the relationship between the two references. If another
    >DictionaryEntr y contains the same key and value references, then the values
    >of
    >the DictionaryEntri es are equal.
    >>
    >Nullable<Tan d DictionaryEntry do not represent tangible objects, they
    >represent assignable values. It helps some people to think of values as
    >conceptual and objects as physical, like me!
    >>
    Anyhow, I think the reason DictionaryEntry is mutable is just because
    people got really sick of saying new DictionaryEntry <MyKeyType,
    MyValueType- not for any good technical reason.
    >>
    >I've never actually created an instance of DictionaryEntry though. I've
    >only
    >used it in iterators so I'm not sure the mutability was necessary. I
    >assume
    >the author(s) made it mutable due to some internal implementation
    >constraints
    >that I don't feel like searching for right now :)
    >>
    >But just to clear things up, my response to Bruce wasn't about the
    >mutability
    >of DictionaryEntry , it was about the mutability of the objects that it
    >references. That's what I assumed Bruce meant when he wrote, "structs that
    >contain references to _mutable_ objects", although reading it
    >out-of-context
    >now I can see why you thought that.
    >>
    I think it's
    hilarious that the structs have the nice immediate-setting semantics
    for mutable structs
    myStructClass foo;
    foo.bar = 1;
    foo.baz = 2;
    //all parameters initialized, it's ready to use, no constructor
    needed.
    >>
    but you can only use them with mutable structs anyways, making it
    completely useless since MS says to never-ever-do-mutable-structs. I
    want the readonly attribute to apply to property setters, and then
    integrate that with the 3.0 Object Initializers so we can have some
    non-excruciating syntax for immutables.
    >>
    >I won't argue that :)
    >>
    >--
    >Dave Sexton
    >>
    >"Martin Z" <martin.zarate@ gmail.comwrote in message
    >news:116223260 6.318831.243760 @b28g2000cwb.go oglegroups.com. ..
    Holy poop, DictionaryEntry is mutable? (off to MSDN).... Whoa. Oh,
    and my comment about avoiding including value-types in structs was from
    the optimization direction - obviously if you're using structs because
    you want value-type semantics then it doesn't matter what you stuff in
    them (as long as they're immutable to avoid confusing aliasing
    behaviour).
    >
    Funny that nullables are value-type - I recently got an error from the
    XmlSerializer where it complained that they were reference types, and
    thus couldn't be XmlAttributes. That was just silly.
    >
    Anyhow, I think the reason DictionaryEntry is mutable is just because
    people got really sick of saying new DictionaryEntry <MyKeyType,
    MyValueType- not for any good technical reason. I think it's
    hilarious that the structs have the nice immediate-setting semantics
    for mutable structs
    myStructClass foo;
    foo.bar = 1;
    foo.baz = 2;
    //all parameters initialized, it's ready to use, no constructor
    needed.
    >
    but you can only use them with mutable structs anyways, making it
    completely useless since MS says to never-ever-do-mutable-structs. I
    want the readonly attribute to apply to property setters, and then
    integrate that with the 3.0 Object Initializers so we can have some
    non-excruciating syntax for immutables.
    >
    Dave Sexton wrote:
    >Hi Bruce,
    >>
    Of course, structs that contain references to _mutable_
    objects can lead to some awfully puzzling, nearly-unpredictable
    behaviour, so I wouldn't recommend that. :-)
    >>
    >DictionaryEntr y is one that doesn't conform to that idea, however.
    >>
    >--
    >Dave Sexton
    >>
    >"Bruce Wood" <brucewood@cana da.comwrote in message
    >news:116222971 1.383330.47510@ b28g2000cwb.goo glegroups.com.. .
    >
    Martin Z wrote:
    >Bruce, My understanding is that, if designed to be entirely immutable
    >and containing no events, then there is almost no visible difference
    >between a class and a struct.
    >
    Apparently so. I'm trying to think of differences in behaviour between
    an immutable class and a struct, and I can't. Of course, it's Monday
    morning. I could be wrong.
    >
    One would have to overload == appropriately (and, one presumes, the
    other mathematical and comparison operators), but apart from that I
    can't think of any hitches. Of course, the temptation with a class
    would be to make it mutable, but that's just a temptation, not a
    given.
    >
    >The struct is primarily a speed-optimization - access to
    >stack-variables
    >is
    >much faster.
    >
    I wouldn't state that as the primary speed benefits of structs. I
    consider the primary speed benefit of structs being that they don't
    need to be garbage-collected. So, if you are doing extensive
    calculations that involve the creation of myriad intermediate results
    then structs are a better choice because objects created on the heap
    have to be GC'd.
    >
    However, this is only one consideration in whether to make something a
    struct or a class, and not even the most important. The most important
    consideration, IMHO, is whether the thing you want to create displays
    _value semantics_. There are lots of discussions in this newsgroup
    about struct vs class and value vs reference semantics.
    >
    >Hence, structs should only contain other valuetypes.
    >
    No, I disagree. For example, I have a struct called a Measure, which
    contains a decimal quantity and a unit of measure. UnitOfMeasure is
    a(n
    immutable) reference type. I think that our disagreement on this point
    goes back to the speed benefits of structs: I see storage on the stack
    as a side-effect of using structs, not as a motivation for using
    structs, and so I don't see anything wrong with structs that contain
    references. Of course, structs that contain references to _mutable_
    objects can lead to some awfully puzzling, nearly-unpredictable
    behaviour, so I wouldn't recommend that. :-)
    >
    >
    >

    Comment

    • Bruce Wood

      #17
      Re: Struct vs Class


      Martin Z wrote:
      Anyhow, I think the reason DictionaryEntry is mutable is just because
      people got really sick of saying new DictionaryEntry <MyKeyType,
      MyValueType- not for any good technical reason. I think it's
      hilarious that the structs have the nice immediate-setting semantics
      for mutable structs
      myStructClass foo;
      foo.bar = 1;
      foo.baz = 2;
      //all parameters initialized, it's ready to use, no constructor
      needed.
      >
      but you can only use them with mutable structs anyways, making it
      completely useless since MS says to never-ever-do-mutable-structs.
      Do they? I've never seen them say this, but then I delve into the MSDN
      doc only when I have to.

      In fact, MS made some more famous mutable structs: Point and Rectangle.
      I think that I understand why they did it: they did it so that people
      could use syntax like this:

      myPoint.X = 7;

      rather than forcing them to create a whole new Point just to change the
      X or Y coordinate, like this:

      Point myPoint = new Point(7, myPoint.Y);

      Yes, the former looks cleaner, but IMHO making Point mutable wasn't
      worth all of the confusion it causes, and they would have been better
      off to force us to use the second syntax.

      I'm sure you know about the confusion: non-intuitive behaviour when a
      property returns a Point, or when a Point is boxed for storage in an
      aggregate structure. Many, many newbies try to use the above p.X = 7
      syntax in those situations and then wonder why their point's
      coordinates didn't change. Yuck.

      I just plain don't indulge in creating mutable structs for questionable
      improvements in syntax. If you want to change one aspect of a struct's
      state, "new" up a new one. You just have to be sure to include a
      constructor that takes all publicly-settable state as arguments.

      Comment

      • Dave Sexton

        #18
        Re: Struct vs Class

        Hi Bruce,

        <snip>
        I'm sure you know about the confusion: non-intuitive behaviour when a
        property returns a Point, or when a Point is boxed for storage in an
        aggregate structure. Many, many newbies try to use the above p.X = 7
        syntax in those situations and then wonder why their point's
        coordinates didn't change. Yuck.
        I just plain don't indulge in creating mutable structs for questionable
        improvements in syntax
        <snip>

        I agree that structs should be immutable, without question. However, many,
        many newbies do many, many things wrong. I'm not sure that I agree with the
        reasoning behind your conclusion. Newbs seems to always catch all exceptions
        too, but we really can't get rid of that functionality either.

        I guess I'd prefer immutability in structs just so shrewd programmers don't
        make the same mistake on accident. I can't think of a better reason than
        that, unfortunately.

        --
        Dave Sexton


        Comment

        • Bruce Wood

          #19
          Re: Struct vs Class


          Dave Sexton wrote:
          Hi Bruce,
          >
          <snip>
          I'm sure you know about the confusion: non-intuitive behaviour when a
          property returns a Point, or when a Point is boxed for storage in an
          aggregate structure. Many, many newbies try to use the above p.X = 7
          syntax in those situations and then wonder why their point's
          coordinates didn't change. Yuck.
          >
          I just plain don't indulge in creating mutable structs for questionable
          improvements in syntax
          <snip>
          >
          I agree that structs should be immutable, without question. However, many,
          many newbies do many, many things wrong. I'm not sure that I agree with the
          reasoning behind your conclusion. Newbs seems to always catch all exceptions
          too, but we really can't get rid of that functionality either.
          >
          I guess I'd prefer immutability in structs just so shrewd programmers don't
          make the same mistake on accident. I can't think of a better reason than
          that, unfortunately.
          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.Loc ation.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.

          Comment

          • Dave Sexton

            #20
            Re: Struct vs Class

            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.Loc ation.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" <brucewood@cana da.comwrote in message
            news:1162256655 .354432.235400@ e3g2000cwe.goog legroups.com...
            >
            Dave Sexton wrote:
            >Hi Bruce,
            >>
            ><snip>
            I'm sure you know about the confusion: non-intuitive behaviour when a
            property returns a Point, or when a Point is boxed for storage in an
            aggregate structure. Many, many newbies try to use the above p.X = 7
            syntax in those situations and then wonder why their point's
            coordinates didn't change. Yuck.
            >>
            I just plain don't indulge in creating mutable structs for questionable
            improvements in syntax
            ><snip>
            >>
            >I agree that structs should be immutable, without question. However, many,
            >many newbies do many, many things wrong. I'm not sure that I agree with
            >the
            >reasoning behind your conclusion. Newbs seems to always catch all
            >exceptions
            >too, but we really can't get rid of that functionality either.
            >>
            >I guess I'd prefer immutability in structs just so shrewd programmers don't
            >make the same mistake on accident. I can't think of a better reason than
            >that, unfortunately.
            >
            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.Loc ation.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.
            >

            Comment

            • Martin Z

              #21
              Re: Struct vs Class

              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 Sexton wrote:
              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.Loc ation.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" <brucewood@cana da.comwrote in message
              news:1162256655 .354432.235400@ e3g2000cwe.goog legroups.com...

              Dave Sexton wrote:
              Hi Bruce,
              >
              <snip>
              I'm sure you know about the confusion: non-intuitive behaviour when a
              property returns a Point, or when a Point is boxed for storage in an
              aggregate structure. Many, many newbies try to use the above p.X = 7
              syntax in those situations and then wonder why their point's
              coordinates didn't change. Yuck.
              >
              I just plain don't indulge in creating mutable structs for questionable
              improvements in syntax
              <snip>
              >
              I agree that structs should be immutable, without question. However, many,
              many newbies do many, many things wrong. I'm not sure that I agree with
              the
              reasoning behind your conclusion. Newbs seems to always catch all
              exceptions
              too, but we really can't get rid of that functionality either.
              >
              I guess I'd prefer immutability in structs just so shrewd programmers don't
              make the same mistake on accident. I can't think of a better reason than
              that, unfortunately.
              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.Loc ation.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.

              Comment

              • Bruce Wood

                #22
                Re: Struct vs Class


                Dave Sexton wrote:
                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.Loc ation.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.Loc ation.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<PointmyLis t = 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?)

                Comment

                • Dave Sexton

                  #23
                  Re: Struct vs Class

                  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.

                  --
                  Dave Sexton


                  Comment

                  • Dave Sexton

                    #24
                    Re: Struct vs Class

                    Hi Bruce,
                    myRectangle.Loc ation.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.Loc ation.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<PointmyLis t = 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.Collect ions.Generic.Li st<System.Drawi ng.Point>.this[int]' because it
                    is not a variable [file] 19 3 [project] }

                    Looks like my proposal might still have some merit :)

                    --
                    Dave Sexton


                    Comment

                    • Bruce Wood

                      #25
                      Re: Struct vs Class


                      Dave Sexton wrote:
                      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<PointmyLis t = 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.Collect ions.Generic.Li st<System.Drawi ng.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. :-)

                      Comment

                      • Martin Z

                        #26
                        Re: Struct vs Class

                        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 Wood wrote:
                        Dave Sexton wrote:
                        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<PointmyLis t = 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.Collect ions.Generic.Li st<System.Drawi ng.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. :-)

                        Comment

                        • Bruce Wood

                          #27
                          Re: Struct vs Class


                          Martin Z wrote:
                          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.

                          Comment

                          • Dave Sexton

                            #28
                            Re: Struct vs Class

                            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.
                            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.NestedVal ue = new Value();

                            ((Value) value.NestedVal ue).Number = 1;

                            Console.WriteLi ne(((Value) value.NestedVal ue).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.Collec tions.Generic.L ist<System.Draw ing.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 ;)

                            --
                            Dave Sexton


                            Comment

                            • Dave Sexton

                              #29
                              Re: Struct vs Class

                              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" <martin.zarate@ gmail.comwrote in message
                              news:1162330195 .041358.231860@ f16g2000cwb.goo glegroups.com.. .
                              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 Wood wrote:
                              >Dave Sexton wrote:
                              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.
                              >>
                              >Unfortunatel y, 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<PointmyLis t = 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.Collect ions.Generic.Li st<System.Drawi ng.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. :-)
                              >

                              Comment

                              • Dave Sexton

                                #30
                                Re: Struct vs Class

                                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


                                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?" ;)

                                --
                                Dave Sexton


                                Comment

                                Working...