【问题标题】:Is there some way to define getters/setters of a private property without having a second property to hold the value?有没有办法在没有第二个属性来保存值的情况下定义私有属性的 getter/setter?
【发布时间】:2012-06-10 16:40:32
【问题描述】:

我有一个公共只读属性,它根据其他几个私有变量计算其值。我从计算的属性中读了很多。计算中使用的变量很少改变。我目前有 readonly 的 getter 执行计算逻辑,但是当因变量发生变化时,将新值推送到属性中对我来说更有意义。

我当前的代码示例:

public class rectangle()
{
    public rectangle(int height, int width)
    {
        _height = height;
        _width = width;
    }

    public void resize(int heightOffset, int widthOffset)
    {
        _height += heightOffset;
        _width += widthOffset;
    }

    private int _height;
    private int _width;
    public int area
    {
        get
        {
            return _height * _width;
        }
    }
}

上面的代码过于简化,但很好地表达了我的意思。 _height 和 _width 可以改变,但他们很少这样做。 area 属性被大量读取...我估计每读取 area 属性超过 100,000 次,_height/_width 就会更改一次。

现在,如果只是乘法,我不会担心...但是实际计算很复杂,而且我发现很多运行时间都陷入了重复的多余计算中。

我知道我可以执行以下操作来修复它:

...

private int _height;
private int height
{
    set
    {
        _height = value;
        _area = _height * _width;
    }
}

private int _width;
private int width
{
    set
    {
        _width = value;
        _area = _height * _width;
    }
}

private int _area;
public int area
{
    get
    {
        return _area;
    }
}

...

鉴于我的情况,上面的代码更有意义,但在我看来,必须有更好的方法来做到这一点。我知道,鉴于我的第一个示例,我可以摆脱属性并将计算逻辑移动到 resize() 方法......但在实际代码中,它不仅仅是影响私有值的一种方法。我不想梳理我的代码来查找每一个出现的地方,而且这样做似乎不可维护。

第二种方法是要走的路吗?我知道它有效,但感觉有些不对劲。我认为我担心的是,将私有属性的值存储到私有变量中似乎很奇怪。我可能想多了。

【问题讨论】:

    标签: c# properties


    【解决方案1】:

    您知道属性访问器可以有访问修饰符吗?

    例如:

    public string A
    {
        internal get
        {
            // Whatever
        }
        private set
        {
            // Whatever
        }
    }
    

    这也适用于自动属性:

    public string A
    {
        internal get;
        private set;
    }
    

    更新: 好的,现在您知道访问器访问修饰符了,那么这个呢?

    public class rectangle()
    {
        public rectangle(int height, int width)
        {
            Height = height;
            Width = width;
        }
    
        public int Height { get; private set; }
        public int Width { get; private set; }
        public int Area { get; private set; }
    
        public void resize(int heightOffset, int widthOffset)
        {
            Height += heightOffset;
            Width += widthOffset;
    
            RefreshArea();
        }
    
        private void RefreshArea() 
        {
            Area = Height * Width;
        }
    }
    

    再见,私有领域!

    【讨论】:

    • 是的,但这无济于事...... setter 仍然需要将它的值存储在某个地方,这意味着一个单独的私有变量。我的问题是我有一个私有变量的私有属性。这似乎有点矫枉过正。
    • @Chronicide 那么将它存储在自动属性、自动实现的底层设置器字段中呢?
    • 不实现 setter 属性排除自动属性?我不能去: public int height{set{height = value; area = height*width;}} 因为那是无限循环的完美示例...
    • @Chronicide 显然这是不可能的。
    • @Chronicide:设置属性后访问属性如何导致无限循环?
    【解决方案2】:

    只要您确保始终使用 height/width 属性来设置 _height 和 _width,您建议的方法就可以使用。

    对私有变量使用私有属性并没有错。它将更新代码移动到一个位置,以便于维护。

    正如 Matías 所说,您可以使您的 height 和 width 属性具有具有不同访问修饰符的访问器,以便您可以读取,但不能写入此类之外的 height/width 属性。

    【讨论】:

      【解决方案3】:

      您说您从代码中的许多地方修改私有字段,而不是总是通过这些字段的属性。最好的情况是,您将访问权限限制为仅调用属性,然后确保您只有几个地方调用重新计算。

      但是,如果您不能或不会这样做,那么您是对的,确保每次修改您的私有字段时都必须确保您计算了相应的字段。

      您还说性能是一个问题,您希望节省调用计算例程的时间。

      因此,如果您必须保留对私有字段的访问权限,也许您可​​以根据这些私有字段何时实际更改来进行重新计算。像这样:

      private int _height; 
      private int height 
      { 
          set 
          { 
              _height = value; 
          } 
      } 
      
      private int _width; 
      private int width 
      { 
          set 
          { 
              _width = value; 
          } 
      } 
      
      private int _area; 
      // have some variables to store the values used in the calculation
      private int _areaWidth;
      private int _areaHeight;
      public int area 
      { 
          get 
          { 
              // only do the calculation if these values have changed
              if(_areaWidth != _width || _areaHeight != _height)
              {
                  _areaWidth = _width;
                  _areaHeight = _height;
                  _area = _width * _height;
              }
              return _area; 
          } 
      } 
      

      这样,您可以将计算保存在一个地方,并且只有在值发生变化时才重新计算。进行条件检查比您实际计算的成本要低。

      【讨论】:

        【解决方案4】:

        我平时的做法是这样的:

        class Rectangle
        {
            int _height;
            public int height { get { return _height; } set { _height = value; _area = null; }}
        
            int _width;
            public int width { get { return _width; } set { _width = value; _area = null; } }
        
            int? _area;
            public int area
            {
                get
                {
                    if(!_area.HasValue)
                        _area = _width * _height;
                    return _area.Value;
                }
            }
        }
        

        【讨论】:

          【解决方案5】:

          注意:为了便于阅读,我将使用传统的 C# 编码风格(即:类名和公共属性的大写字母)。我建议你也这样做

          解决问题的一种方法是简单地使 Rectangle 类不可变,并使用 Lazy<T> 来延迟计算昂贵的东西:

          public class Rectangle
          {
              private readonly Lazy<int> _area;
          
              public Rectangle(int height, int width)
              {
                  Height = height;
                  Width = width;
                  _area = new Lazy<int>(() => Height * Width);
              }
          
              public int Height { get; private set; }
          
              public int Width { get; private set; }
          
              public int Area
              {
                  get
                  {
                      return _area.Value;
                  }
              }
          }
          

          不可变类/结构的好处数不胜数。不利的一面是,每当您想“更改”Rectangle 对象时,您都必须使用new 或Rectangle 的静态成员,例如:

          ...
          public static Rectangle Widen(Rectangle rect, int widenByAmount)
          {
              return new Rectangle(rect.Height, rect.Width + widenByAmount);
          }
          ...
          

          如果您有 LinqPad,请尝试以下操作:

          void Main()
          {
              Rectangle r = new Rectangle(5, 10);
              r.Height.Dump();
              r.Width.Dump();
              r.Area.Dump();
              r.Area.Dump();
              r = Rectangle.Widen(r, 10);
              r.Area.Dump();
              r.Area.Dump();
          }
          
          // Define other methods and classes here
          public class Rectangle
          {
              private readonly Lazy<int> _area;
          
              public Rectangle(int height, int width)
              {
                  Height = height;
                  Width = width;
                  _area = new Lazy<int>(() =>
                      {
                          "Calculating...".Dump();
                          return Height * Width;
                      });
              }
          
              public int Height { get; private set; }
          
              public int Width { get; private set; }
          
              public int Area
              {
                  get
                  {
                      return _area.Value;
                  }
              }
          
              public static Rectangle Widen(Rectangle rect, int widenByAmount)
              {
                  return new Rectangle(rect.Height, rect.Width + widenByAmount);
              }
          }
          

          对我来说,它吐出来了:

          5
          10
          Calculating...
          50
          50
          Calculating...
          100
          100
          

          【讨论】:

          • 嘿,但这将工作一次...第二次访问 Area 将更新其值??
          • 我想你的意思是_area = new Lazy...
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-03-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-14
          相关资源
          最近更新 更多