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

Как я могу написать стенограмму следующего сценария?

get
{
    if (_rows == null)
    {
        _rows = new List<Row>();
    }

    return _rows;
}

person ErrorAgain    schedule 08.07.2016    source источник
comment
Ваш код в порядке. Его можно сократить, но за счет удобочитаемости. На мой взгляд, экономить 3 строчки не стоит.   -  person Phil K    schedule 08.07.2016
comment
Я не без ума от этой модели. У вас есть геттер, который производит изменение состояния   -  person Brad Thomas    schedule 08.07.2016
comment
stackoverflow.com/questions/13876066/   -  person Brad Thomas    schedule 08.07.2016
comment
@BradThomas В некоторых моделях это не так уж плохо. Пример в вопросе немного похож на ленивую оценку: get не изменяет внешнее состояние объекта. Пока к _rows нет доступа откуда-то еще, то есть...   -  person KABoissonneault    schedule 08.07.2016
comment
Как вы думаете, какую выгоду вы получите, сократив его каким-либо образом?   -  person ClickRick    schedule 08.07.2016
comment
@KABoissonneault И пока этот объект никогда не читается несколькими потоками одновременно   -  person Tavian Barnes    schedule 08.07.2016
comment
@TavianBarnes Никто не говорил, что настоящий шаблон легко сделать правильно, просто   -  person KABoissonneault    schedule 08.07.2016
comment
@Тавиан. Проблемы куда более коварны. Как правило, потребитель геттера может предположить, что объект находится в одном и том же состоянии до и после чтения свойства. В противном случае могут возникнуть неожиданные побочные эффекты, нарушающие принцип наименьшего удивления.   -  person Brad Thomas    schedule 08.07.2016
comment
@BradThomas Согласен, но определенно возможно выполнить такую ​​ленивую инициализацию таким образом, чтобы вы не могли наблюдать изменение состояния. Если вы все сделаете правильно, то это может быть достойная оптимизация.   -  person Tavian Barnes    schedule 08.07.2016
comment
Чтобы вернуть значение, которое может быть нулевым, вы пишете return someValueThatMightBeNull;. Хотите перефразировать вопрос?   -  person user253751    schedule 10.07.2016
comment
Просто инициализируйте поле в своем конструкторе, чтобы оно никогда не было null.   -  person CodesInChaos    schedule 10.07.2016
comment
@CodesInChaos Если _rows не является общедоступным или не установлен null каким-либо методом в классе. Эти вещи также должны быть изменены.   -  person PC Luddite    schedule 10.07.2016
comment
@PCLuddite Оба из них были бы ужасным дизайном, поэтому их все равно следует исправить.   -  person CodesInChaos    schedule 10.07.2016
comment
В этом конкретном случае часто лучше сделать _rows доступным только для чтения и инициализировать его в конструкторе.   -  person Phil1970    schedule 12.07.2016
comment
И если свойство инициализируется таким образом, вы должны знать о возможном побочном эффекте, таком как оценка свойства во время отладки.   -  person Phil1970    schedule 12.07.2016


Ответы (10)


Используя оператор объединения с нулевым значением ( ?? ):

get 
{ 
     _rows = _rows ?? new List<Row>(); 
     return _rows; 
}

ИЛИ (менее читаемо):

get { return _rows ?? (_rows = new List<Row>()); }

?? оператор называется оператором объединения с нулевым значением. Он возвращает левый операнд, если операнд не равен нулю; в противном случае возвращается правый операнд.

person Zein Makki    schedule 08.07.2016
comment
вернуть _rows ?? (_rows = новый список‹Строка›()); - person eocron; 08.07.2016
comment
Итак, вам удалось сделать код менее читаемым, по сути, просто втиснув все вместе в одну строку в одно большое выражение, и вы не даете никаких объяснений того, какую пользу это может иметь. Без объяснений я не понимаю, чем этот ответ полезен, по крайней мере, для тех, кто заботится о написании поддерживаемого кода. - person ; 09.07.2016
comment
@hvd предоставленная документация ?? более чем понятно. Я не вижу необходимости перефразировать документы clear своими словами. - person Zein Makki; 09.07.2016
comment
@ user3185569 Я знаю, что делает ??. Это ясно. Что неясно, так это то, какую пользу это имеет по сравнению с кодом, который ОП уже задал в вопросе. - person ; 09.07.2016
comment
@hvd ОП хотел сокращение, это именно то, что дает этот ответ. - person Zein Makki; 09.07.2016
comment
Хорошо, если вы только заботитесь о том, чтобы сделать код короче, независимо от того, остается ли он читабельным и удобным для сопровождения, вы можете сократить его еще больше, изменив _rows на односимвольное имя поля и удалив ненужные пробелы. . - person ; 09.07.2016
comment
@hvd, значит, тебе не нравится ??? Здесь не место обсуждать, полезна ли эта функция с точки зрения удобочитаемости. - person Zein Makki; 09.07.2016
comment
Ну, я думаю, что в этом случае это все еще вполне читабельно, и если вы знаете оператор ??, это не должно быть проблемой для его понимания. - person Stefan; 09.07.2016
comment
[C# ignoramus here] Приведет ли 1-й пример к какому-либо избыточному самоназначению или в действительности он не будет оптимизирован? - person underscore_d; 10.07.2016
comment
@hvd Этот код совершенно ясен (даже для такого Java-программиста, как я). Однострочное выражение немного сложнее понять, чем любую из восьми строк ОП, но оно все же довольно простое. Это отдельная вещь, поэтому проблем с обслуживанием быть не может, если сопровождающий понимает синтаксис языка. - person maaartinus; 10.07.2016
comment
@maaartinus Одна из очевидных проблем с обслуживанием - это когда вам нужно расширить функцию для инициализации поля до чего-то, что нельзя записать в одном выражении. Вы не можете. Вам нужно сначала преобразовать его обратно в то, что было у OP в первую очередь. - person ; 10.07.2016
comment
@underscore_d Я не уверен, будет ли он оптимизирован (моя машина с Windows не работает, поэтому я не могу проверить это прямо сейчас), но это определенно приводит к избыточному самоназначению, если оно вообще не оптимизировано. - person PC Luddite; 10.07.2016
comment
@PCLuddite Мне было бы интересно узнать, поймает ли это оптимизатор, если вы сможете попробовать его позже. Похоже, что это сильно зависит от того, как реализованы неоптимизированные инструкции и в каком порядке работает оптимизатор. Или он даже ищет такие вещи! - person underscore_d; 10.07.2016
comment
@hvd: Не будь абсурдом. Вы помещаете свою сложную инициализацию в частный метод и вызываете его в одном выражении, как и раньше. - person Kevin; 11.07.2016

Это шаблон ленивой инициализации, поэтому проще всего использовать Lazy‹T› класс.

class Foo
{
    Lazy<List<Row>> _rows;

    public Foo()
    {
        _rows = new Lazy(() => new List<Row>());
    }

    public List<Row> Rows
    {
        get { return _rows.Value; }
    }
}

Это имеет дополнительное преимущество, заключающееся в том, что он не «загрязняет» геттер логикой инициализации.

person Roman Reiner    schedule 08.07.2016
comment
Хотя создание списка настолько дешево, что это глупо. - person CodesInChaos; 10.07.2016
comment
Я пошел с тем, что ОП использовал в своем вопросе. Я предположил, что это всего лишь пример, а реальный объект более тяжелый. - person Roman Reiner; 10.07.2016

Я предлагаю оператор тройной

get {
  return _rows == null ? _rows = new List<Row>() : _rows;
}

Или, поскольку пустое List<Row> не создает больших накладных расходов, почему бы не избавиться от явного поля _row и реализовать свойство только для чтения (синтаксис C# 6.0):

public IList<Row> Rows {get;} = new List<Row>();
person Dmitry Bychenko    schedule 08.07.2016
comment
Решение C # 6 - это путь. - person Arturo Torres Sánchez; 09.07.2016
comment
Для C# 6 я предпочитаю обычно помещать new в конструкторы для упрощения отладки. - person Phil1970; 12.07.2016

Вот идея получше: не допустить, чтобы _rows когда-либо становилось null.

Заставьте ваш конструктор инициализировать переменную:

public MyClass()
{
    this._rows = new List<Row>();
}

и тогда ваша собственность просто

get
{
    return this._rows;
}

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

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

person jpmc26    schedule 08.07.2016
comment
В случае с пустым списком, как здесь, особой разницы нет. Однако в целом это не всегда так. Что делать, если инициализация объекта занимает больше времени, и вы (как автор библиотеки) не уверены, запрашивал ли он когда-либо вызывающую программу. Тогда этот метод приведет к слишком большим накладным расходам, и ленивая загрузка — это путь. - person Mr Lister; 10.07.2016
comment
Чем проще, тем лучше. Пользователям никогда не нужно проверять значение null, и вам не нужно это документировать. По крайней мере, для List (я согласен с Lister), и для первой версии, если вы не знаете иного, это, вероятно, преждевременная оптимизация (или что-то автоматизированное может сделать для вас). Не тратьте время на размышления об этом. - person toddkaufmann; 10.07.2016
comment
@MrLister Если ленивая загрузка требуется из-за некоторых накладных расходов на производительность, то этот вопрос был выбран неправильно, чтобы заменить его инициализацией пустого списка; более подходящим выбором было бы что-то вроде MyExpensiveToInitializeClass. Этот ответ относится к вопросу, как написано, и предполагает, что нет никаких причин для этого, кроме того, что написано. Если есть, то это другой вопрос. - person jpmc26; 11.07.2016

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

get
{
    return _rows ?? (_rows = new List<Row>());
}

Стоит отметить, что это тот тип изменений, которые ReSharper отлично предлагает (они называют это быстрое исправление).

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

Преобразовать в '??'  выражение

Пара кликов позже, и изменение реализовано.

введите здесь описание изображения

person Richard Ev    schedule 08.07.2016

Например, вот так:

get{ return _rows ?? (_rows = new List<Row>()); }
person eocron    schedule 08.07.2016

Если вы хотите, чтобы ваш код вел себя как ваш текущий код, лениво инициализируя ваше резервное поле при доступе к свойству, тогда да, вы можете сделать его короче. Вы можете переименовать свое резервное поле, поскольку в ответе уже используется ??, чтобы поместить все в одно выражение, и когда у вас есть это единственное выражение, используйте новый синтаксис свойств С# 6, чтобы избежать написания get и return:

List<Row>_;List<Row> Rows=>_??(_=new List<Row>());

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

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

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

{Type} {Field};
public {Type} {Property} {
  get {
    if ({Field} == null) {
      {Field} = new {Type}();
    }
    return {Field};
  }
}

где ваш редактор затем предложит вам указать конкретный {Тип}, {Поле}, {Свойство}, без необходимости вводить его каждый раз снова.

person Community    schedule 09.07.2016

Если бы вы действительно хотели сократить его, я бы просто удалил лишние скобки.

    get
    {
        if (_rows == null)
            _rows = new List<Row>();

        return _rows;
    }
person Tyler Nichols    schedule 08.07.2016
comment
лучше не надо, правда - person edc65; 08.07.2016
comment
Я не уверен, почему за это проголосовали. Конечно, на самом деле он ничего не делает, кроме сокращения исходного кода, но все же. Многие считают, что удаление фигурных скобок из операторов if с одним оператором — это хорошо. - person Mr Lister; 10.07.2016
comment
@MrLister На самом деле не лучше. Это тот же код. - person PC Luddite; 10.07.2016
comment
@PCLuddite Это, конечно, короче, не так ли? :) - person Tyler Nichols; 11.07.2016
comment
это лучший ответ - person Below the Radar; 12.07.2016

Вы можете сделать это любым из следующих способов:

  • Условный оператор (?:)
  • Оператор объединения с нулевым значением ( ?? )

С условным оператором

get {
  return _rows == null ? new List<Row>() : _rows;
}

Оператор объединения с нулевым значением

get { 
  return _rows ?? new List<Row>(); 
}
person Abdul Moiz Khan    schedule 30.07.2016

person    schedule
comment
или короче: вернуть _rows == null? новый список‹Строка›: _rows - person nosbor; 08.07.2016
comment
@nosbor оба из них продолжают возвращать новый экземпляр списка, поскольку они оставляют _rows неназначенными, что отличается от поведения, которое запрашивает OP. - person Andy; 09.07.2016