10

I seem to be using this sort of pattern in my code a lot , I know that it is not a simple Autoproperty any more as that would be:

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

The code I have been using is this:

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

This is so I can just do MainClass.BCSFilters and not worry about needing to instantiate the List in the consuming code. Is this a 'normal' pattern \ the correct way to do this?

I couldn't find a duplicate question

4

10 回答 10

33

This is a technique that I use a lot myself. This can also help save memory resources since it doesn't instantiate the List<> object unless the objects property is actually being used within the consuming code. This uses a "Lazy Loading" technique.

Also, the "Lazy Loading" technique that you listed isn't Thread Safe. If there happens to be multiple calls simultaneously to the property you could end up having multiple calls setting the property to a new List<> object, consequentially overwriting any existing List values with a new, empty List<> object. To make the Get accessor Thread Safe you need to use the Lock statement, like so:

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

However, if you'll always need the List<> object instantiated it's a little simpler to just create it within the object constructor and use the automatic property instead. Like the following:

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

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

Additionally, if you leave the "set" accessor public then the consuming code will be able to set the property to Null which can break other consuming code. So, a good technique to keep the consuming code from being able to set the property value to Null is to set the set accessor to be private. Like this:

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

A related technique is to return an IEnumerable<> object from the property instead. This will allow you to replace the List<> type internally within the object at any time and the consuming code will not be affected. To return IEnumerable<> you can just return the plain List<> object directly since it implements the IEnumerable<> interface. Like the following:

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

    public IEnumerable<BCSFilter> BCSFilters { get; set; }
}
于 2009-11-24T12:09:05.040 回答
10

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

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

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

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

于 2009-11-24T12:32:50.610 回答
5

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

private IList<BCSFilter> _BCSFilters;

public IList<BCSFilter> BCSFilters
{
    get
    {
        return _BCSFilters ?? (_BCSFilters = new List<BCSFilter>());
    }
    set
    {
        _BCSFilters = value;
    }
}
于 2009-11-24T21:59:09.497 回答
5

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

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

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

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

于 2009-11-24T12:12:44.143 回答
3

Your approach is the lazy init version of

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

    public IList<BCSFilter> BCSFilters { get; set; }
}
于 2009-11-24T12:08:41.260 回答
3

还有一个技巧:)

使用 .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

于 2012-12-04T14:44:42.000 回答
2

这是延迟加载模式的一个示例。这是一种公认​​的模式并且完全有效。

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

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

于 2009-11-24T12:13:29.900 回答
1

Yeah, that's perfectly normal ;-)

Such lazy creation is not uncommon, and makes good sense. The only caveat is you'll have to be careful if you reference the field at all.

Edit: But I'll have to go with Chris and the others: it's a (much) better pattern to use an auto property and initialize the collection in the constructor.

于 2009-11-24T12:09:38.200 回答
0

We use that pattern at my workplace. It's handy because you avoid possible null reference exceptions in the consuming code, and keeps the consuming code simpler.

于 2009-11-24T12:08:33.927 回答
0

It's an alright pattern. Autoproperties is just shorthand for the very most simple, and arguably most common scenario with properties, and for some scenarios, it simply isn't sensible to use it. What you could do, tho, is to instantiate BCSFilters in the constructor instead. That way you could use autoproperties and you still wouldn't have to worry about null reference exceptions.

于 2009-11-24T12:09:43.740 回答