【问题标题】:C# Auto Property - Is this 'pattern' best practice?C# Auto Property - 这是“模式”的最佳实践吗?
【发布时间】:2009-11-24 12:04:53
【问题描述】:

我似乎在我的代码中经常使用这种模式,我知道它不再是一个简单的 Autoproperty:

  public IList<BCSFilter> BCSFilters { get; set; }

我一直使用的代码是这样的:

    private IList<BCSFilter> _BCSFilters;

    /// <summary>
    /// Gets or sets the BCS filters.
    /// </summary>
    /// <value>The BCS filters.</value>
    public IList<BCSFilter> BCSFilters
    {
        get
        {
            if (_BCSFilters == null)
            {
                _BCSFilters = new List<BCSFilter>();
            }

            return _BCSFilters;
        }
        set
        {
            _BCSFilters = value;
        }
    }

这样我就可以只做 MainClass.BCSFilters 而不必担心需要在消费代码中实例化 List。这是一个“正常”模式\这样做的正确方法吗?

我找不到重复的问题

【问题讨论】:

    标签: c# design-patterns automatic-properties


    【解决方案1】:

    这是我自己经常使用的一种技术。这也有助于节省内存资源,因为它不会实例化 List 对象,除非在使用代码中实际使用了 objects 属性。这使用了“延迟加载”技术。

    此外,您列出的“延迟加载”技术不是线程安全的。如果同时对属性进行多次调用,您最终可能会多次调用将属性设置为新的 List 对象,从而用新的空 List 对象覆盖任何现有的 List 值。要使 Get 访问器线程安全,您需要使用 Lock statement,如下所示:

    private IList<BCSFilter> _BCSFilters;
    
    // Create out "key" to use for locking
    private object _BCSFiltersLOCK = new Object();
    
    /// <summary>
    /// Gets or sets the BCS filters.
    /// </summary>
    /// <value>The BCS filters.</value>
    public IList<BCSFilter> BCSFilters
    {
        get
        {
            if (_BCSFilters == null)
            {
                // Lock the object before modifying it, so other
                // simultaneous calls don't step on each other
                lock(_BCSFiltersLOCK)
                {
                    if (_BCSFilters == null)
                    }
                        _BCSFilters = new List<BCSFilter>();
                    }
                }
            }
    
            return _BCSFilters;
        }
        set
        {
            _BCSFilters = value;
        }
    }
    

    但是,如果您总是需要实例化 List 对象,那么在对象构造函数中创建它并使用自动属性会更简单一些。像下面这样:

    public class MyObject
    {
        public MyObject()
        {
            BCSFilters = new List<BCSFilter>();
        }
    
        public IList<BCSFilter> BCSFilters { get; set; }
    }
    

    此外,如果您将“set”访问器设为公开,则使用代码将能够将该属性设置为 Null,这可能会破坏其他使用代码。因此,防止消费代码将属性值设置为 Null 的一个好方法是将 set 访问器设置为私有。像这样:

    public IList<BCSFilter> BCSFilters { get; private set; }
    

    一个相关的技术是从属性中返回一个 IEnumerable 对象。这将允许您随时在对象内部替换 List 类型,并且使用代码不会受到影响。要返回 IEnumerable 您可以直接返回普通的 List 对象,因为它实现了 IEnumerable 接口。像下面这样:

    public class MyObject
    {
        public MyObject()
        {
            BCSFilters = new List<BCSFilter>();
        }
    
        public IEnumerable<BCSFilter> BCSFilters { get; set; }
    }
    

    【讨论】:

    • 唯一的缺点是这个初始化不是线程安全的,另见 Rob Levine 的回复。 (如果您将其合并到您的回复中会很好。)
    • peterchen,感谢您的建议。
    • +1 我们曾经称其为“按需创建”,但我想“延迟加载”是当今更常见的描述方式。不过模式非常好。
    • @peterchen - 通常实例成员默认不应该是线程安全的,除非它们是专门为多线程操作而设计的。 List&lt;T&gt; 类本身不是线程安全的,因此将其实例化为线程安全几乎没有意义——任何安全都需要来自更高级别的抽象。
    • 由于链式复杂性,请勿在构造函数中初始化变量。意外忘记在派生类中调用基构造函数可能会导致“未初始化”异常。坚持在 getter 中设置它。
    【解决方案2】:

    您的模式是完全合理的延迟加载模式,但请注意它不是线程安全的

    如果两个线程第一次非常接近地访问此属性,则您的空检查模式不会阻止一个线程评估为空但之前它有机会初始化列表的情况,第二个线程的计算结果也为 null。在这种情况下,它们都会初始化列表。

    此外,有可能在属性返回时,一个线程将拥有一份列表副本,而第二个线程拥有另一个。

    在单线程环境中不是问题,但绝对需要注意。

    【讨论】:

    • 谢谢 Rob,我们目前没有使用多线程,但我同意如果我们能尽早考虑这些事情,以后会省心
    【解决方案3】:

    只要你想要的是正确的模式:

    • 允许外部代码替换整个列表(instance.BCSFilters = null)
    • 在读取时神奇地创建列表。虽然这很棘手,因为您允许用户将其设置为 null(在集合中),但您不允许它保持为 null(因为稍后获取将产生一个空列表)。

    您还可以希望以只读模式公开 IList(如果需要,可以使用惰性初始化),因此用户只能在其中添加或删除项目,而不能覆盖列表本身。如果你有很多作业,可能会涉及到复制。

    通常我的 IList 成员只有一个 getter,我什至可以公开 IEnumerable 或在 get 中返回一个副本(但你必须提供特定的 Add 和 Remove 方法,所以 YMMV)

    【讨论】:

    • +1 - 懒惰地实例化一个列表是可以的,但是没有太多好的理由允许你的类之外的代码替换你的整个列表实例。
    【解决方案4】:

    仅供参考,一种更简洁的方式来做你已经在做的事情可能看起来像这样:

    private IList<BCSFilter> _BCSFilters;
    
    public IList<BCSFilter> BCSFilters
    {
        get
        {
            return _BCSFilters ?? (_BCSFilters = new List<BCSFilter>());
        }
        set
        {
            _BCSFilters = value;
        }
    }
    

    【讨论】:

    • 哦,是的。 Coalesce 运算符大大简化了事情。 +1
    【解决方案5】:

    你的方法是惰性初始化版本

    public class xyz
    {
        public xyz()
        {
            BCSFilters = new List<BCSFilter>();
        }
    
        public IList<BCSFilter> BCSFilters { get; set; }
    }
    

    【讨论】:

      【解决方案6】:

      还有一个技巧:)

      使用 .Net 4 中的 Lazy。

      我认为我在 Mark Seemann 博客上看到过这个:

       public class Order
      {
          public Order()
          {
              _customerInitializer = new Lazy<Customer>(() => new Customer());
          }
      
          // other properties
      
          private Lazy<Customer> _customerInitializer;
          public Customer Customer
          {
              get
              {
                  return _customerInitializer.Value;
              }
          }
      
          public string PrintLabel()
          {
              string result = Customer.CompanyName; // ok to access Customer
              return result + "\n" + _customerInitializer.Value.Address; // ok to access via .Value
          }
      }
      

      注意“_customerInitializer”永远不能为空,所以使用它是一样的。 它也可以是线程安全的! 构造函数可以通过 LazyThreadSafetyMode 枚举获得重载! http://msdn.microsoft.com/en-us/library/system.threading.lazythreadsafetymode.aspx

      【讨论】:

        【解决方案7】:

        这是Lazy Load pattern 的一个示例。这是一种公认​​的模式并且完全有效。

        您可以在 C# 中使用自动属性并将实例分配给构造函数中的属性。延迟加载模式的好处是除非调用它,否则您不会初始化该属性。这在初始化成本很高的情况下很有用。

        我更喜欢带有构造函数初始化的自动属性,因为语法更简洁,输入更少除非初始化很昂贵,在这种情况下延迟加载效果很好。

        【讨论】:

        • 感谢 Dariom 的链接,我认为它一定是某种模式......现在我知道它是延迟加载
        【解决方案8】:

        是的,这很正常;-)

        这种懒惰的创作并不少见,而且很有道理。唯一需要注意的是,如果您完全引用该字段,则必须小心。

        编辑:但我不得不和 Chris 和其他人一起去:使用自动属性并在构造函数中初始化集合是一​​种(好得多)更好的模式。

        【讨论】:

          【解决方案9】:

          我们在我的工作场所使用这种模式。这很方便,因为您避免了消费代码中可能出现的空引用异常,并使消费代码更简单。

          【讨论】:

            【解决方案10】:

            这是一个不错的模式。 Autoproperties 只是最简单,可以说是最常见的属性场景的简写,对于某些场景,使用它根本不明智。但是,您可以做的是在构造函数中实例化 BCSFilters。这样您就可以使用自动属性,而且您仍然不必担心空引用异常。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-02-27
              • 1970-01-01
              • 1970-01-01
              • 2017-09-01
              • 2011-05-18
              • 1970-01-01
              相关资源
              最近更新 更多