0

I keep running into cases where I want my class properties to behave as if they were structs. (edit: actually, I misunderstood how structs work. I do not want my property to behave exactly like a struct; I want it to behave differently when it is manipulated vs. copied.)

I am wondering if this is possible, and if yes, whether it would be a good idea.

Suppose I have 2 simple classes:

public class Cube
{
    public Point Center {get ; set;}
    ... // some more cube properties
}

public class Point 
{
    public virtual Double X {get {return this.x;} set{this.x = value;}}
    public virtual Double Y {...}
    public virtual Double Z {...}
    ...
}

Now I want to access Cube.Center like this:

Cube cube = new Cube();
cube.Center.X += 10.0; // cube1 should move 10.0 in X direction

Point point = cube.Center;
point.X += 10.0 // cube1 should NOT move here

Cube cube2 = new Cube();
cube2.Center = point;
cube2.Center.Y += 5.0; // cube2 should move, but cube1 should stay in place. 

As far as I understand, if Point where a struct instead of a class, it would be passed by value and the above would be the expected behavior. (edit: I misunderstood how structs work. Something like cube.Center.X += 10.0 is not posible with a struct)

However:

  • mutable structs are evil
  • the objects may contain more fields than this small example, Microsoft recommends structs only with a very small memory footprint
  • most importantly: a struct cannot inherit from a class, so things like this are not possible with a struct: Having a private child class of Point for the cube center, that does some extra stuff whenever it is changed.

    public class Cube
    {
    
       // private child of Point, can only be used by Cube
       private class cubeCenter: Point
       {
           private Cube cube; // The cube this point belongs to
    
           public override double X 
           {
               get {return base.X} 
    
               // custom setter: whenever X changes, 
               // recalculate the bounding box of the cube
               set {base.X=value; cube.recalculateBoundingBox();} 
           }
           ... // + other overrides & constructor
       }
    
       private cubeCenter _center; 
    
       public Point Center 
       {
           get {return this._center} 
           set {this._center = new cubeCenter(value, this);}
       }
    
       ...
    }
    
    
    
    cube1.Center.X += 10.0; // now cube1 moves AND its bounding box is recalculated.
    

So the behavior I am seeking is this: - It should be possible to modify sub-properties like cube1.Center.X. - Whenever this happens, I want mys custom cubeCenter setter to execute. - However, when someone grabs the whole Center: Point p = cube1.Center, the assigned object should just be a normal Point without connection to cube

Is this possible, perhaps with an implicit conversion operator?

Is this a bad design choice? I am open for other suggestions.

(One thing I considered is making Points immutable to avoid all these problems. However I cannot find a non-ugly syntax for changing the center in this case:)

    // this is the immutable equivalent to cube1.Center.X += 10.0
    cube1.Center = cube1.Center.WithX ( cube1.Center.X + 10.0 )
Community
  • 1
  • 1
HugoRune
  • 13,157
  • 7
  • 69
  • 144

4 Answers4

2

I would favour an immutable struct Point. Moving it is simple:

public static Point operator +(Point a, Point b) 
{
   return new Point(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
}
⋮
cube1.Center = cube1.Center + new Point(10, 0, 0);

Everything else should fall into place quite nicely.

Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
1

You can write a clone method and make the copy explicit, which is good. For the sake of readability you have to make a clear distinction where you are copying or not, otherwise debug the code could be a nightmare.

public class Point
{
    public virtual Double X {get {return this.x;} set{this.x = value;}}
    public virtual Double Y {...}
    public virtual Double Z {...}

    public Point Clone() {
       return this.MemberwiseClone();
    }
}


Point point = cube.Center.Clone();
point.X += 10.0 // cube1 will NOT move here

The MemberwiseClone will make a shallow copy of the object, you have to clone nested reference types by yourself.

public class Cube
{
   public Point Center {get ; set;}

   public Cube Clone() {
       var cube = this.MemberwiseClone();
       cube.Center = this.Center.Clone();
       return cube;
   }

}

Marcelo De Zen
  • 9,439
  • 3
  • 37
  • 50
0

To make real value semantics, just keep a single Point instance that is never passed out and don't ever replace it (you need to add clone functionality to Point):

public class Cube {
    private readonly Point center = new Point();

    public Point Center {
        get {
            return center.Clone();
        }
        set {
            center.X = value.X;
            center.Y = value.Y;
        }
    }
    ... // some more cube properties
}

Note that these semantics prevent you from using the cube.Center.X = ... code - but that is true as well for structs.

Lucero
  • 59,176
  • 9
  • 122
  • 152
  • That's the immutable approach, but then things like `cube1.Center.X += 10.0;` will not work anymore, I'd like to be able to move a cube to a new position without syntax like this `cube1.Center = cube1.Center.WithX ( cube1.Center.X + 10.0 );` – HugoRune Aug 12 '12 at 08:27
  • also, this means that whenever someone accesses the center like this: `cube1.Center.X + cube1.Center.Y + cube1.Center.Z` three Point clones are generated. Since this is a class used for a lot of cpu-bound calculations, I'd like to avoid that overhead. – HugoRune Aug 12 '12 at 08:32
  • Your assumption that `cube.Center.X += 10.0` works with structs is wrong - you cannot get both. This is why the .NET WinForms have both `Location` and `Size` properties but also single `Top`, `Left`, `Width` and `Height` properties. The error message you get when you try to change just `X` is the following: "Cannot modify the expression because it is not a variable". – Lucero Aug 12 '12 at 08:55
  • I see, I missed that about structs. I guess that is part of the reason why they are evil. I definitely do not want to copy that particular behavior. – HugoRune Aug 12 '12 at 09:00
0

It's impossible to get exactly what you want, because the getter for the property can't know if the value will be used for manipulation or for copying.

You can encapsulate the point, and provide a method for cloning it and properties for manipulating it:

public class Cube {
  private Point _center;
  public Point CopyCenter() { return _center.Clone(); }
  public double CenterX { get { return _center.X; } set { _center.X = value; } }
  public double CenterY { get { return _center.Y; } set { _center.Y = value; } }
  public double CenterZ { get { return _center.Z; } set { _center.Z = value; } }
}

The syntax for the usage will not be exactly the same, but at least it's clear what's happening.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005