Предпочтительный метод для установки значения свойства только для получения: конструктор vs поле поддержки

Asked
Viewd1262

3

Изменить : хотя я принял Ответ Дэвида , Также следует учитывать ответ Джона .

Какой метод предпочтительнее для установки значения свойства только для чтения (только для получения?): с использованием резервного поля или с помощью конструктора? Предположим, что проект предназначен для свойства, а не для поля (в будущем может произойти обновление, которое потребует, чтобы у свойства был установщик, который исключил бы использование поля).

Какой метод предпочтительнее, исходя из следующего простого примера? Если одно предпочтительнее другого, почему?

Вариант 1 (резервное поле) :

 class SomeObject
{
    // logic
}

class Foo
{
    private SomeObject _myObject;
    public SomeObject MyObject
    {
        get
        {
            if( _myObject == null )
            {
                _myObject = new SomeObject();
            }
            return _myObject;
        }
    }

    public Foo()
    {
        // logic
    }
}
 

Вариант 2 (конструктор) :

 class SomeObject
{
    // logic
}

class Foo
{
    public SomeObject MyObject { get; private set; }

    public Foo()
    {
        MyObject = new SomeObject();
        // logic 
    }
}
 

4 ответов

7

Это зависит от времени, необходимого для "new SomeObject ();" и вероятность того, что получатель вообще будет вызван.

Если создание MyObject дорого и не будет использоваться каждый раз, когда вы создаете экземпляр Foo (), вариант 1 - хорошая идея, и это называется ленивой инициализацией. Такие программы, как Google Chrome, активно используют его для сокращения времени запуска.

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

2

В последнее время я неравнодушен к этому подходу:

 class Foo
{
    public SomeObject MyObject { get; private set; }

    public Foo()
    {
        MyObject = new SomeObject();
    }
}
 
  • вы также можете сделать во внутреннем.

    Eric Schneider26 июня 2009, 06:34
  • Я тоже занимаюсь этим в последнее время.

    David Basarab26 июня 2009, 04:00
5

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

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

 public class Person
{
    private readonly string name;
    public string Name { get { return name; } }

    public Person(string name)
    {
        this.name = name;
    }
}
 

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

Чтобы вернуться к вашей конкретной ситуации, я обычно пишу:

 class Foo
{
    private readonly MyObject myObject;
    public SomeObject MyObject { get { return myObject; } }

    public Foo()
    {
        myObject = new MyObject();
        // logic 
    }
}
 

То есть, если только строительство SomeObject не является особенно дорогим. Это просто проще, чем делать это лениво в собственности и потенциально беспокоиться о проблемах с потоками.

Я предполагал, что неизменяемость - полезная цель, но вы говорили о добавлении установщика. Я не уверен, почему вы думаете, что это исключает использование поля, но это определенно не так. Например, вы можете объединить ленивое создание экземпляра из вашего первого фрагмента кода и иметь такой сеттер:

 class Foo
{
    private SomeObject _myObject;
    public SomeObject MyObject
    {
        get
        {
            if( _myObject == null )
            {
                _myObject = new SomeObject();
            }
            return _myObject;
        }
        set
        {
            // Do you want to only replace it if
            // value is non-null? Or if _myObject is null?
            // Whatever logic you want would go here
            _myObject = value;
        }
    }

    public Foo()
    {
        // logic
    }
}
 

Я считаю, что основными решениями должны быть:

  • Вы хотите, чтобы тип был правильно неизменяемым?
  • Вам нужна отложенная инициализация?
  • Вам нужна какая-либо другая логика в ваших свойствах?

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

0

Лично я бы сделал первое, но переместил инициализацию в конструктор.

 class Foo
{
    private SomeObject myObject;

    public SomeObject MyObject
    {
        get { return myObject; }
    }

    public Foo()
    {
        myObject = new SomeObject();
    }
}
 

Почему? Я лично не использую private set, просто потому, что тогда мне нужно помнить, какие свойства имеют поддерживающие переменные, а какие нет, что означает, что во всем коде класса некоторые поля используют нижний регистр, а некоторые - верхний. Думаю, эти незначительные "несоответствия" беспокоят меня с ОКР.

Это всего лишь незначительное предпочтение стиля. Шесть из одного, полдюжины из другого. Я ничего не имею против варианта №2 и не возражаю против идиомы private set.

  • @John - вы правы в том, что суть вопроса не была конкретно в ленивой инициализации; Скорее, какой метод предпочтительнее… когда использовать один вместо другого и наоборот.

    Metro Smurf26 июня 2009, 05:27
  • В чем именно заключается преимущество этого? Он всегда инициализирует myObject (теряет любую отложенную загрузку) и избегает автопропа без видимой причины.

    Matthew Flaschen26 июня 2009, 03:29
  • Я предположил, что аспект отложенной инициализации не был главной темой вопроса. Если бы это было важно, вариант 2 не подходил бы.

    John Kugelman26 июня 2009, 03:38