【问题标题】:Are there any reasons to use private properties in C#?有什么理由在 C# 中使用私有属性吗?
【发布时间】:2011-03-19 14:52:36
【问题描述】:

我刚刚意识到 C# 属性构造也可以与 private 访问修饰符一起使用:

private string Password { get; set; }

虽然这在技术上很有趣,但我无法想象我什么时候会使用它,因为私人领域甚至涉及更少的仪式

private string _password;

我无法想象我什么时候需要能够在内部 get 而不是 set设置但不获取私有字段:

private string Password { get; }

private string Password { set; }

但也许有一个使用嵌套/继承类的用例,或者get/set可能包含逻辑而不是仅仅返回属性的值,尽管我倾向于保持属性严格简单并让显式方法执行任何逻辑,例如GetEncodedPassword().

是否有人出于任何原因在 C# 中使用私有属性,或者它只是技术上可能但很少在实际代码中使用的结构之一?

附录

很好的答案,通读它们我挑选出私有财产的这些用途:

  • 私有字段需要延迟加载时
  • 当私有字段需要额外的逻辑或者是计算值时
  • 因为私有字段可能难以调试
  • 为了“向自己出示合同”
  • 作为序列化的一部分在内部转换/简化公开的属性
  • 包装要在类中使用的全局变量

【问题讨论】:

标签: c# properties access-modifiers


【解决方案1】:

如果我需要缓存一个值并想延迟加载它,我会使用它们。

private string _password;
private string Password
{
    get
    {
        if (_password == null)
        {
            _password = CallExpensiveOperation();
        }

        return _password;
    }
}

【讨论】:

  • +1 这也是我的标准用法!正如@Reed Copsey 之前提到的,我也喜欢稍后添加额外逻辑的能力。
  • 一个很好的常见模式是return _password ?? (_password = CallExpensiveOperation());
  • @Marc 从 C# 6 开始,您甚至不必编写 get 和 return。 private string Password => _password ?? (_password = CallExpensiveOperation());
  • 使用 C# 8 更短:private string Password => _password ??= CallExpensiveOperation();
  • @Bas 这不是延迟加载,因为 CallExpensiveOperation(); 在包含对象的构造/初始化期间被调用,而不是在第一次访问属性时调用。
【解决方案2】:

正如其他人所提到的,我的代码中 this 的主要用途是延迟初始化。

私有属性优于字段的另一个原因是私有属性比私有字段更容易调试。我经常想知道诸如“这个字段被意外设置;谁是第一个设置这个字段的调用者?”如果你可以在设置器上放一个断点并点击 go,那就更容易了。你可以在那里登录。您可以将性能指标放在那里。您可以进行在调试版本中运行的一致性检查。

基本上,归结为:代码比数据强大得多。任何能让我编写所需代码的技术都是一种很好的技术。字段不允许您在其中编写代码,属性可以。

【讨论】:

  • “代码远比数据强大”是你的说法吗?谷歌搜索它会返回指向你的引用。只是想知道,以便我可以在需要时正确引用它。
  • @Joan:我不知道。要么是我编的,要么是我听到别人说的,然后想“哇,我应该完全偷那个,然后忘记我从谁那里偷的。”
【解决方案3】:

也许存在嵌套/继承类的用例,或者 get/set 可能包含逻辑而不是仅仅返回属性值

即使我不需要属性的 getter 或 setter 的逻辑,我个人也会使用它。使用属性,甚至是私有属性,确实有助于您的代码在未来得到验证,以便您以后可以在需要时将逻辑添加到 getter。

如果我觉得一个属性最终可能需要额外的逻辑,我有时会将它包装到一个私有属性中而不是使用一个字段,这样我以后就不必更改我的代码了。


在一个半相关的情况下(尽管与您的问题不同),我经常在公共属性上使用私有设置器:

public string Password 
{
    get; 
    private set;
}

这为您提供了一个公共 getter,但将 setter 保持为私有。

【讨论】:

  • +1 是有道理的:“如果我觉得某个属性最终可能需要额外的逻辑,我有时会将它包装到私有属性中而不是使用字段,这样我就不必更改稍后我的代码。”
【解决方案4】:

私有获取属性的一个很好的用法是计算值。有几次我有私有只读属性,只是对我类型中的其他字段进行计算。它不值得一个方法,对其他类也不感兴趣,所以它是私有属性。

【讨论】:

    【解决方案5】:

    延迟初始化是它们可以整洁的地方,例如

    private Lazy<MyType> mytype = new Lazy<MyType>(/* expensive factory function */);
    
    private MyType MyType { get { return this.mytype.Value; } }
    
    // In C#6, you replace the last line with: private MyType MyType => myType.Value;
    

    然后你可以在任何地方写:this.MyType 而不是this.mytype.Value 并封装它在一个地方被延迟实例化的事实。

    遗憾的是,C# 不支持将支持字段限定为属性(即在属性定义中声明)以完全隐藏它并确保只能通过属性访问它。

    【讨论】:

    • 同意在那里有范围界定方面。
    • 我经常使用同样的技术,我也希望一个字段可以限定为代码体。这是一个不错的功能,但优先级较低。
    • @Eric Lippert - field-declaration 的范围在 accessor-declarations 内,在我的 C# 愿望清单中排名第一。如果你能在未来的某个(实际)版本中设计和实现它,那么我会给你烤蛋糕。
    【解决方案6】:

    我能想到的唯一一种用法

    private bool IsPasswordSet 
    { 
         get
         {
           return !String.IsNullOrEmpty(_password);
         }
    }
    

    【讨论】:

    • +1 表示从其他私有变量计算的有用属性类
    • 为什么不使用私有方法private bool IsPasswordSet() { return !String.IsNullOrEmpty(_password); }
    【解决方案7】:

    属性和字段不是一对一的。属性是关于类的接口(无论是公共接口还是内部接口),而字段是关于类的实现。属性不应被视为仅公开字段的一种方式,它们应被视为公开类的意图和目的的一种方式。

    就像您使用属性向您的消费者提供关于什么构成您的类的合同一样,您也可以出于非常相似的原因向自己提供合同。所以是的,我确实在有意义的时候使用私有属性。有时私有属性可以隐藏诸如延迟加载之类的实现细节,属性实际上是多个字段和方面的集合,或者属性需要在每次调用时虚拟实例化(想想DateTime.Now)。有时候,即使在课程的后端对自己强制执行此操作也是有意义的。

    【讨论】:

    • +1:“您也可以出于非常相似的原因向自己提出合同”是有道理的
    【解决方案8】:

    我在序列化中使用它们,例如 DataContractSerializer 或 protobuf-net 支持这种用法(XmlSerializer 不支持)。如果您需要将对象简化为序列化的一部分,这很有用:

    public SomeComplexType SomeProp { get;set;}
    [DataMember(Order=1)]
    private int SomePropProxy {
        get { return SomeProp.ToInt32(); }
        set { SomeProp = SomeComplexType.FromInt32(value); }
    }
    

    【讨论】:

      【解决方案9】:

      我一直在做的一件事是将“全局”变量/缓存存储到HttpContext.Current

      private static string SomeValue{
        get{
          if(HttpContext.Current.Items["MyClass:SomeValue"]==null){
            HttpContext.Current.Items["MyClass:SomeValue"]="";
          }
          return HttpContext.Current.Items["MyClass:SomeValue"];
        }
        set{
          HttpContext.Current.Items["MyClass:SomeValue"]=value;
        }
      }
      

      【讨论】:

        【解决方案10】:

        我使用私有属性来减少访问经常使用的子属性的代码。

            private double MonitorResolution
            {
                get { return this.Computer.Accesories.Monitor.Settings.Resolution; }
            }
        

        如果有很多子属性,这很有用。

        【讨论】:

          【解决方案11】:

          我不时使用它们。当您可以轻松地在属性中放置断点或添加日志语句等时,它们可以使调试变得更容易。

          如果您以后需要以某种方式更改数据类型或需要使用反射,这也很有用。

          【讨论】:

          • 同上;如果获取/设置涉及逻辑,我有时可能会使用私有或受保护的属性。这一般取决于逻辑的多少:简单的逻辑我会在属性里做,很多的逻辑我通常会使用一个辅助函数。使代码最易于维护的因素。
          【解决方案12】:

          我知道这个问题已经很老了,但以下信息不在当前的任何答案中。

          我无法想象我什么时候需要能够在内部获取但不设置

          如果您要注入依赖项,您可能希望在属性上使用 Getter 而不是 setter,因为这表示只读属性。换句话说,Property 只能在构造函数中设置,不能被类中的任何其他代码更改。

          Visual Studio Professional 还将提供有关属性而非字段的信息,以便更轻松地查看正在使用的字段。

          【讨论】:

            【解决方案13】:

            通常的做法是只使用 get/set 方法修改成员,即使是私有方法。现在,这背后的逻辑是让您知道您的 get/set 始终以特定方式运行(例如,触发事件),这似乎没有意义,因为这些不会包含在属性方案中......但是旧习惯很难改掉。

            【讨论】:

              【解决方案14】:

              当有与属性集或获取相关的逻辑(想想延迟初始化)并且该属性在类中的一些地方使用时,这是非常有意义的。

              如果它只是一个直接的支持字段?没有什么是好的理由。

              【讨论】:

                【解决方案15】:

                好吧,正如没有人提到的那样,您可以使用它来验证数据或锁定变量。

                • 验证

                  string _password;
                  string Password
                  {
                      get { return _password; }
                      set
                      {
                          // Validation logic.
                          if (value.Length < 8)
                          {
                              throw new Exception("Password too short!");
                          }
                  
                          _password = value;
                      }
                  }
                  
                • 锁定

                  object _lock = new object();
                  object _lockedReference;
                  object LockedReference
                  { 
                      get
                      {
                          lock (_lock)
                          {
                              return _lockedReference;
                          }
                      }
                      set
                      {
                          lock (_lock)
                          {
                              _lockedReference = value;
                          }
                      }
                  }
                  

                  注意:锁定引用时,您不会锁定对被引用对象成员的访问。

                延迟参考:当延迟加载时,您可能最终需要异步执行,现在有AsyncLazy。如果您使用的是 Visual Studio SDK 2015 之前的版本或未使用它,您也可以使用 AsyncEx's AsyncLazy

                【讨论】:

                  【解决方案16】:

                  另一种用法是在设置值时做一些额外的操作。

                  在我的情况下,它发生在 WPF 中,当我显示一些基于私​​有对象的信息时(未实现 INotifyPropertyChanged):

                  private MyAggregateClass _mac;
                  
                  private MyAggregateClass Mac
                  {
                      get => _mac;
                      set
                      {
                          if(value == _mac) return;
                          _mac = value;
                          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DisplayInfo)));
                      }
                  }
                  
                  public string DisplayInfo => _mac.SomeStringInformationToDisplayOnUI;
                          
                  

                  也可以有一些私有方法,比如

                  private void SetMac(MyAggregateClass newValue)
                  

                  这样做。

                  【讨论】:

                    【解决方案17】:

                    显式字段的一些更奇特的用途包括:

                    • 您需要使用 refout 与值 - 可能是因为它是一个 Interlocked 计数器
                    • 旨在表示基本布局,例如具有显式布局的 struct(可能映射到 C++ 转储或 unsafe 代码)
                    • 从历史上看,该类型已与 BinaryFormatter 一起使用并具有自动字段处理功能(更改为 auto-props 会更改名称并因此破坏序列化程序)

                    【讨论】:

                      【解决方案18】:

                      查看指南 (Properties (C# Programming Guide)) 似乎没有人希望将属性用作私有成员。

                      属性使类能够公开获取和设置值的公共方式,同时隐藏实现或验证代码。

                      在任何情况下,它都可以通过一种或两种方法互换,反之亦然。

                      所以原因可能是在 getting 上保留括号并在 setting 上获取字段语法。

                      【讨论】:

                        【解决方案19】:

                        各种答案都提到了使用属性来实现惰性成员。 this answer 讨论了使用属性制作实时别名。我只是想指出,这两个概念有时会同时出现。

                        当使用一个属性为另一个对象的公共属性创建别名时,该属性的惰性被保留:

                        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
                        private IDbConnection Conn => foo.bar.LazyDbConnection;
                        

                        另一方面,在构造函数中检索该属性将否定惰性方面:

                        Conn = foo.bar.LazyDbConnection;
                        

                        【讨论】:

                          猜你喜欢
                          • 2011-06-19
                          • 1970-01-01
                          • 2012-01-08
                          • 2015-11-08
                          • 1970-01-01
                          • 2010-09-20
                          • 1970-01-01
                          相关资源
                          最近更新 更多