【问题标题】:Should I use a Field or Property within the class to set values我应该在类中使用字段还是属性来设置值
【发布时间】:2012-12-12 03:47:40
【问题描述】:

于是我就一段代码与一位同事发生了友好的争论:

public sealed class NewObject
{
    private string _stuff = string.Empty;

    public string Stuff
    {
        get { return GetAllStuff(); }
    }

    private string GetAllStuff()
    {
        //Heavy string manipulation of _stuff
    }

    public NewObject(string stuffToStartWith)
    {
        _stuff = stuffToStartWith;
    }

    public static NewObject operator +(NewObject obj1, NewObject obj2)
    {
        if (obj1 == null)
            throw new ArgumentNullException();

        if (obj2 == null)
            throw new ArgumentNullException();

        NewObject result = new NewObject(string.Empty);
        result._stuff = String.Concat(obj1._stuff, obj2._stuff);

        return result;
    }
}

参数超过了运算符覆盖。我的同事认为,在构造函数之外的任何地方设置私有字段的值并不是最佳编程实践。我的同事提出的解决方案是将Stuff 属性的名称重构为AllStuff 并添加一个属性Stuff,该属性具有getset 访问器并使用新的Stuff运算符覆盖中的属性。让它看起来像这样:

    public static NewObject operator +(NewObject obj1, NewObject obj2)
    {
        if (obj1 == null)
            throw new ArgumentNullException();

        if (obj2 == null)
            throw new ArgumentNullException();

        NewObject result = new NewObject(string.Empty);
        result.Stuff = String.Concat(obj1.Stuff, obj2.Stuff);

        return result;
    }

我不同意。我觉得第一种方法更好,因为它使属性在课堂外保持只读。我的问题是,哪种方式是面向对象设计的最佳实践?

【问题讨论】:

    标签: c#


    【解决方案1】:

    您可以在属性上给自己一个private set(这将保留可见性或缺乏可见性,同时允许您使用属性语法),但这并不能真正解决问题。

    在课堂上,我说变量是公平的游戏。外部的任何地方,包括继承的类,都应该 getset 属性,但在声明类中我说可以分配私有成员。

    【讨论】:

      【解决方案2】:

      你是对的

      err... 详细地说,您的私有变量是您的,您可以随心所欲。如果有人对您进行了更改对象值的操作(尤其是像 + 之类的操作),那么在构造函数之外修改值并没有错。这就是他们私密的全部意义所在。

      除非您希望它不可变...

      更新
      我想得越多,我就越相信您的同事将“私有”变量与“常量”变量混淆了——或者可能将这两个概念合并。没有理由私有变量必须在对象的整个生命周期中保持不变,这就是您的朋友似乎所暗示的。 const 代表不变,private 只代表对象,它们是两种截然不同的模式。

      更新2
      此外,如果突然你的对象不仅仅是一个字符串,而且变量是交织在一起的(想想一个字符串对象,它有一个 char* 和一个 len,并且必须一起维护),他的设计就会崩溃。您想要的最后一件事是用户必须处理对象的内部变量。让对象成为一个对象并保持其自身的内部值并向用户呈现单个实体。

      【讨论】:

      • @cyber:我认为这更多是因为 OP 并不完全清楚。在我看来,他的同事在提倡不要直接在构造函数或属性实现之外更改私有变量。我不认为他提倡在对象的生命周期内保持相同的值。
      【解决方案3】:

      一般问题与合同政策有关。

      (公共集)属性的概念是,当它被调用时,除了改变状态的语义概念之外,还可以采取其他动作。例如,调用 setter 可能会触发事件、触发外围设备等。

      您的同事说,如果不使用该属性,您就是在回避合同,不会触发任何事件。

      所以从你同事的角度来看,你应该这样做:

      this.Prop = CalculateSomeValue();
      if (this.Prop < kPropMin) {
          this.Prop = kPropMin;
      }
      else if (this.Prop > kPropMax * 2) {
          this.Prop = kPropMax * 2;
      }
      this.Prop = this.Prop / 2;
      

      现在,这是一个人为的案例,但我刚刚在 get 中击中了一个可能的重量级属性,在 get 中达到了 3 次,在 set 中达到了 3 次,其中一个可能是非法的(设置为 kHighLimit / 2 )。我可以通过使用本地并在最后精确调用一次来解决这个问题。不过,我宁愿只是把这个领域弄得一团糟。

      我认为更好的方法是务实地使用它:当且仅当您想调用 set 或 get 的所有副作用时才在类中使用该属性,否则请遵循该属性的精神。

      -- 澄清-- 按照属性的精神,假设我设置的属性是这样的:

      bool PropValueOutOfRange(int val) {
          return val < kPropMin || val > kPropMax;
      }
      
      public int Prop {
          set {
              if (PropValueOutOfRange(value))
                  throw new ArgumentOutOfRangeException("value");
              if (PropValueConflictsWithInternalState(value))
                  throw new ArgumentException("value");
              _prop = value;
              NotifyPeriperalOfPropChange(_prop);
              FirePropChangedEvent(/* whatever args might be needed */);
          }
      }
      

      在此,我已经排除了很多蹩脚的细节,但这让我可以重复使用它们。所以现在我对接触私有字段 _prop 充满信心,因为我有相同的基础设施来确保将其保持在范围内并通知外围设备并触发事件。

      这让我可以编写这段代码:

      _prop = CalculateSomeValue();
      
      if (_prop < kPropMin)
          _prop = kPropMin;
      else if (_prop > kPropMax * 2)
          _prop = kPropMax;
      
      _prop /= 2;
      
      NotifyPeripheralOfPropChange();
      FirePropChangedEvent();
      

      我使用的工具与建造房产时使用的工具相同,因此我本着房产的精神工作。我保持正确的范围(但不要投掷 - 我更清楚,我是实施者),命中外围和触发事件,并且我深思熟虑、可读且高效地执行此操作 - 而不是不分青红皂白。

      【讨论】:

      • +1 因为在阅读完这篇文章后,我认为这可能是我同事的思考过程。但是,出于对代码有争议的目的,不得出于任何原因在课堂外更改该属性。
      • 问题:在您的务实方法中,当您说“服从属性的精神”时,您的意思是如果属性永远不应该在类之外更改,那么就像我正在做的那样使用该字段进行更新上面?
      【解决方案4】:

      我看不出他的方法有什么好处。

      【讨论】:

        【解决方案5】:

        我个人更喜欢没有字段,因此我使用自动实现的private 属性而不是private 字段和public-getprivate-set 属性,如果想要有public 只读属性。 如果我必须向属性添加代码,我仍然只使用属性访问器内部的字段,并在其他任何地方使用 getter 和 setter,包括构造函数。
        如果我需要 readonly 字段,我也必须使用字段,但 C# 4.0 将引入只读属性。


        此外,我还可以通过使用以下代码来避免整个问题。

        public static NewObject operator +(NewObject obj1, NewObject obj2)
        {
            return new NewObject(String.Concat(obj1.Stuff, obj2.Stuff));
        }
        

        我喜欢的实现是这样的。

        public sealed class NewObject
        {
            private String Stuff { get; set; }
        
            // Use a method instead of a property because the operation is heavy.       
            public String GetAllStuff()
            {
                // Heavy string manipulation of this.Stuff.
                return this.Stuff;
            }
        
            // Or lets use a property because this.GetAllStuff() is not to heavy.
            public String AllStuff
            {
                get { return this.GetAllStuff(); }
            }
        
            public NewObject(String stuffToStartWith)
            {
                this.Stuff = stuffToStartWith;
            }
        
            public static NewObject operator +(NewObject obj1, NewObject obj2)
            {
                // Error handling goes here.
        
                return new NewObject(String.Concat(obj1.Stuff, obj2.Stuff);
            }
        }
        

        【讨论】:

        • 我是否误解了您所说的“只读属性”是什么意思?现在我可以在没有设置器的情况下编写属性,使其成为只读...
        猜你喜欢
        • 2011-09-12
        • 1970-01-01
        • 2011-11-29
        • 2017-07-04
        • 2010-11-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多