【问题标题】:pass-by-value semantics for properties属性的按值传递语义
【发布时间】:2012-08-08 21:11:14
【问题描述】:

我经常遇到我希望我的类属性表现得像结构一样的情况。 (编辑:实际上,我误解了结构的工作原理。我不希望我的属性表现得完全像一个结构;我希望它在被操作和复制时表现不同。)

我想知道这是否可能,如果可以,这是否是个好主意。

假设我有 2 个简单的类:

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 {...}
    ...
}

现在我想像这样访问 Cube.Center:

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. 

据我了解,如果 Point where 是一个结构而不是一个类,它将按值传递,上面将是预期的行为。 (编辑:我误解了结构的工作原理。像cube.Center.X += 10.0 这样的东西不适用于结构)

但是:

  • mutable structs are evil
  • 对象可能包含比这个小示例更多的字段,Microsoft 建议仅使用内存占用非常小的结构
  • 最重要的是:一个结构不能从一个类继承,所以这样的事情对于一个结构是不可能的:拥有一个 Point 的私有子类作为立方体中心,每当它改变时都会做一些额外的事情。

    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.
    

所以我正在寻求的行为是这样的: - 应该可以修改像cube1.Center.X 这样的子属性。 - 每当发生这种情况时,我希望我的自定义 cubeCenter setter 执行。 - 但是,当有人抓住整个中心时:Point p = cube1.Center,分配的对象应该只是一个普通的Point,没有连接到立方体

这可能吗,也许使用隐式转换运算符?

这是一个糟糕的设计选择吗?我愿意接受其他建议。

(我考虑过的一件事是使 Points 不可变以避免所有这些问题。 但是,在这种情况下,我找不到用于更改中心的不难看的语法:)

    // this is the immutable equivalent to cube1.Center.X += 10.0
    cube1.Center = cube1.Center.WithX ( cube1.Center.X + 10.0 )

【问题讨论】:

    标签: c# oop design-patterns properties


    【解决方案1】:

    我倾向于不可变的struct Point。移动它很简单:

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

    其他一切都应该很好地到位。

    【讨论】:

      【解决方案2】:

      您可以编写一个克隆方法并使副本显式化,这很好。为了便于阅读,您必须明确区分您在哪里复制或不复制,否则调试代码可能是一场噩梦。

      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
      

      MemberwiseClone 会做一个对象的浅拷贝,你必须自己克隆嵌套的引用类型。

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

      }

      【讨论】:

        【解决方案3】:

        要创建真正的值语义,只需保留一个永远不会传递且永远不会替换它的 Point 实例(您需要将克隆功能添加到 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
        }
        

        请注意,这些语义会阻止您使用 cube.Center.X = ... 代码 - 但对于结构也是如此。

        【讨论】:

        • 这是不可变的方法,但是像 cube1.Center.X += 10.0; 这样的东西将不再起作用,我希望能够在没有像 cube1.Center = cube1.Center.WithX ( cube1.Center.X + 10.0 ); 这样的语法的情况下将立方体移动到新位置
        • 另外,这意味着每当有人像这样访问中心时:cube1.Center.X + cube1.Center.Y + cube1.Center.Z 会生成三个 Point 克隆。由于这是一个用于大量 cpu-bound 计算的类,我想避免这种开销。
        • 您认为cube.Center.X += 10.0 与结构一起使用的假设是错误的——您不能同时获得两者。这就是为什么 .NET WinForms 既具有 LocationSize 属性,又具有单个 TopLeftWidthHeight 属性的原因。当您尝试更改 X 时收到的错误消息如下:“无法修改表达式,因为它不是变量”。
        • 我明白了,我错过了关于结构的内容。我想这就是他们邪恶的部分原因。我绝对不想复制这种特殊行为。
        【解决方案4】:

        不可能准确地得到你想要的,因为属性的 getter 无法知道该值是用于操作还是用于复制。

        您可以封装该点,并提供克隆它的方法和操作它的属性:

        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; } }
        }
        

        用法的语法不会完全相同,但至少很清楚发生了什么。

        【讨论】:

          猜你喜欢
          • 2014-10-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-10-21
          • 2021-06-01
          相关资源
          最近更新 更多