Предпочтительный метод для установки значения только для получения. Свойство: конструктор против вспомогательного поля. - PullRequest
3 голосов
/ 26 июня 2009

Редактировать : Хотя я принял ответ Дэвида , ответ Джона также должен быть рассмотрен.

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

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

Вариант 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 голосов
/ 26 июня 2009

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

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

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

5 голосов
/ 26 июня 2009

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

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

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

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

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

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

2 голосов
/ 26 июня 2009

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

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

    public Foo()
    {
        MyObject = new SomeObject();
    }
}
0 голосов
/ 26 июня 2009

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

class Foo
{
    private SomeObject myObject;

    public SomeObject MyObject
    {
        get { return myObject; }
    }

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

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

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

...