C# Auto Property-это "шаблон" лучшая практика?

Кажется, я много использую такой шаблон в своем коде , я знаю, что это уже не простая Автопропертия, как это было бы:

  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 и не беспокоиться о необходимости создания экземпляра списка в потребляющем коде. Является ли это "нормальным" шаблоном правильным способом сделать это?

Я не мог найти дубликат вопроса

10 ответов


это техника, которую я сам часто использую. Это также может помочь сохранить ресурсы памяти, так как он не создает экземпляр объекта List, если свойство objects фактически не используется в потребляющем коде. Это использует метод "ленивой загрузки".

кроме того, метод" ленивой загрузки", который вы перечислили, не является потокобезопасным. Если происходит несколько вызовов одновременно к свойству, вы можете в конечном итоге иметь несколько вызовов, устанавливающих свойство в новый объект List , последующая перезапись всех существующих значений списка новым пустым объектом List. Чтобы сделать поток доступа Get безопасным, вам нужно использовать оператор Lock, например:

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, немного проще просто создать его в конструкторе объектов и вместо этого использовать свойство automatic. Например:

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

ваш шаблон является полностью разумным шаблоном ленивой нагрузки, но имейте в виду, что это не потокобезопасными.

Если два потока получили доступ к этому свойству в первый раз очень близко друг к другу, ваш шаблон проверки null не предотвратит условие, при котором один поток оценивается как null, но до он получает возможность инициализировать список, второй поток также равен нулю. В этой ситуации они оба инициализируют список.

кроме того, существует вероятность того, что к моменту возвращения свойства один поток будет иметь одну копию списка, а второй-другую.

не проблема в однопоточной среде, но наверняка что-то знать.


Это правильный шаблон, пока то, что вы хотите:

  • разрешить внешнему коду заменить весь список (экземпляр.BCSFilters = null)
  • список волшебным образом создан на чтение. Это сложно, хотя и так, поскольку вы позволяете пользователю установить его в null (в наборе), но вы не позволяете ему оставаться null (так как более поздний get даст пустой список).

вы также можете выставить IList в режиме только для чтения (с ленивым init если вы хотите), поэтому пользователи могут добавлять или удалять только элементы в нем, не имея возможности перезаписать сам список. Если у вас много назначений, может быть задействовано копирование.

обычно у меня есть только геттер для моих членов IList, и я могу даже выставить IEnumerable или вернуть копию в get (но вам нужно будет указать конкретные методы добавления и удаления, поэтому YMMV)


FYI, несколько более краткий способ сделать то же самое, что вы уже делаете, может выглядеть так:

private IList<BCSFilter> _BCSFilters;

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

ваш подход-ленивая версия init

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

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

есть еще одна уловка :)

использовать Lazy из .Net 4.

Я видел это в блоге Марка 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


Это пример ленивый шаблон загрузки. Это общепринятая модель и вполне обоснованная.

можно использовать автоматические свойства в C# и назначить экземпляр свойству в конструкторе. Преимущество шаблона ленивой загрузки заключается в том, что вы не инициализируете свойство, если оно не вызвано. Это может быть полезно в ситуациях, когда инициализация дорого.

Я предпочитаю автоматические свойства с инициализацией конструктора, потому что синтаксис более лаконичный, и меньше ввода если инициализация стоит дорого, и в этом случае ленивая нагрузка работает хорошо.


мы используем этот шаблон на моем рабочем месте. Это удобно, потому что вы избегаете возможных исключений нулевой ссылки в потребляющем коде и упрощаете потребляющий код.


Да, это совершенно нормально ;-)

такое ленивое создание не является редкостью и имеет смысл. Единственное предостережение-вам нужно быть осторожным, если вы вообще ссылаетесь на поле.

Edit: но мне придется пойти с Крисом и другими: это (гораздо) лучший шаблон для использования свойства auto и инициализации коллекции в конструкторе.


Это хороший шаблон. Autoproperties-это просто стенография для самого простого и, возможно, самого распространенного сценария со свойствами, а для некоторых сценариев просто нецелесообразно его использовать. Вместо этого вы можете создать экземпляр BCSFilters в конструкторе. Таким образом, вы можете использовать autoproperties, и вам все равно не придется беспокоиться об исключениях нулевой ссылки.