Оператор объединения свойств для C #

9

Нулевой оператор в c # позволяет сократить код

  if (_mywidget == null)
     return new Widget();
  else
     return _mywidget;

Вниз до:

  return _mywidget ?? new Widget();

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

  if (_mywidget == null)
     return 5;
  else
     return _mywidget.Length;

С:

  return _mywidget.Length ??! 5;

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

Бен Фултон
источник
1
Достаточно ли здесь условного оператора?
Анон.
1
Кто-то написал что-то, что позволяет вам сделать что-то вроде этого: string location = employee.office.address.location ?? «Неизвестный»; , Это установит местоположение как «Неизвестно», когда один из объектов ( сотрудник , офис , адрес или местоположение ) будет нулевым. К сожалению, я не помню, кто это написал и где он это написал. Если я найду это снова, я опубликую это здесь!
Кристоф Клас
1
Этот вопрос получит гораздо большую популярность в StackOverflow.
Работа
2
??!является оператором в C ++. :-)
Джеймс МакНеллис
3
Привет 2011, только что позвонил, чтобы сказать, что c # получает безопасный оператор навигации
Натан Купер

Ответы:

5

Я очень хочу функцию языка C #, которая позволяет мне безопасно выполнять x.Prop.SomeOtherProp.ThirdProp, не проверяя каждый из них на ноль. В одной кодовой базе, где мне приходилось много выполнять такие «глубокие обходы свойств», я написал метод расширения под названием Navigate, который проглотил исключения NullReferenceExceptions и вместо этого возвратил значение по умолчанию. Так что я мог бы сделать что-то вроде этого:

var propVal = myThing.Navigate(x => x.PropOne.PropTwo.PropThree.PropFour, defaultValue);

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

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

RationalGeek
источник
1
Точно то, что я думал, но производительность безопасного способа была бы довольно плохой, я думаю (из-за множества Expression.Compile ()) ... Жаль, что он не реализован в C # (что-то вроде Obj.?PropA.?Prop1)
Guillaume86
x.PropOne.PropTwo.PropThree.PropFour - плохой выбор дизайна, поскольку он нарушает закон Деметры. Перепроектируйте ваши методы / классы, чтобы вам не нужно было это использовать.
Джастин Шилд
Бездумно отказываться от глубокого доступа к собственности в свете Закона Деметры так же опасно, как и бездумная нормализация базы данных. Вы можете зайти слишком далеко.
Мир
3
В C # 6.0 это возможно с помощью нуль-условного оператора ( так называемый оператор Элвиса) msdn.microsoft.com/en-us/magazine/dn802602.aspx
victorvartan
15

Я верю, что вы сможете сделать это с C # 6 довольно просто:

return _mywidget?.Length ?? 5;

Обратите внимание на нулевой условный оператор ?.слева. _mywidget?.Lengthвозвращает ноль, если _mywidgetноль.

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

Крис Нолет
источник
9

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

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

return (_mywidget != null) ? _mywidget.Length : 5;

?? это просто ярлык для условного оператора.

как зовут
источник
5
Лично это так же плохо (imho), как если бы оператор делал это в первую очередь. Вы хотите что-то от объекта ... этого объекта может и не быть ... поэтому давайте предоставим 5ответ, если его там нет. Вы должны посмотреть на свой дизайн, прежде чем начать спрашивать специальных операторов.
Moo-Juice
1
@ Moo-Juice Я просто представляю себе человека, который поддерживает этот код: «Почему это дает мне 5? Hrmmm. Какая?!'
msarchet
4

Это выглядит как троичный оператор:

return (_mywidget != NULL) ? _mywidget : new Widget();
Мартин Йорк
источник
3

Лично я думаю, что это до читабельности. В первом примере ??перевод довольно красиво звучит как «Ты здесь? Нет, давай сделаем это».

Во втором примере ??!явно WTF и ничего не значит для программиста .... объект не существует, поэтому я не могу получить это свойство, поэтому я верну 5. Что это вообще значит? Что такое 5? Как мы приходим к выводу, что 5 является хорошим значением?

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

Moo-Сок
источник
2
Ну, вы должны игнорировать тот факт, что ??! не существует Представь, если ??! было распространено, и если оно использовалось, то принимать решения на основе этого.
whatsisname
3
Это напоминает мне о так называемом «операторе WTF» в C ++ (на самом деле это работает:. (foo() != ERROR)??!??! cerr << "Error occurred" << endl;)
Тамас Селеи,
@whatsisname, это было больше отражением того факта, что оно подходило для принимаемого решения. Создавать объект, потому что его там не было, имеет смысл. Назначить произвольное значение, которое ничего не значит для читателя, потому что вы не можете получить свойство чего-либо, действительно является сценарием WTF.
Moo-Juice
Я думаю, что вы попали в мой пример. Возможно, 0 лучше, чем 5. Возможно, свойство является объектом, поэтому, если _mywidget имеет значение null, вы хотите вернуть новый foodle (). Я полностью не согласен с этим ?? более читабельно, чем ??! хотя :)
Бен Фултон
@ Бен, хотя я согласен с тем, что сосредоточение внимания на самом операторе не слишком усложняет ваш вопрос, я проводил параллель с восприятием программиста и этим произвольным 5. Я действительно думаю, что есть еще одна проблема, которая заключается в том, что вы должны сделать что-то вроде этого, тогда как в вашем первом примере необходимость совершенно очевидна, особенно в таких вещах, как менеджеры кэша.
Moo-Juice
2

Я думаю, что люди слишком увязли в «5», которые вы возвращаете ... возможно, 0 было бы лучше :)

Во всяком случае, я думаю, что проблема в том, что на ??!самом деле это не отдельный оператор. Подумайте, что бы это значило:

var s = myString ??! "";

В этом случае это не имеет смысла: это имеет смысл, только если левый операнд является средством доступа к свойству. Или как насчет этого:

var s = Foo(myWidget.Length) ??! 0;

Там есть средство доступа к свойствам, но я все еще не думаю, что это имеет смысл (если myWidgetесть null, значит ли это , что мы вообще не вызываем Foo()?), Или это просто ошибка?

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

Дин Хардинг
источник
1

Кто-то создал служебный класс, который сделает это за вас. Но я не могу найти это. То, что я нашел, было что-то похожее на форумах MSDN (посмотрите на второй ответ).

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

Майкл Браун
источник
1

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

Например, у вас есть объекты, связанные с картой, и вы хотите, чтобы они были на нужной высоте. Карты DTED могут иметь дыры в своих данных, поэтому можно иметь нулевое значение, а также значения в диапазоне от -100 до ~ 8900 метров. Вы можете захотеть что-то вроде:

mapObject.Altitude = mapObject.Coordinates.DtedAltitude ?? DEFAULT_ALTITUDE;

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

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

Берин Лорич
источник
1

Когда я не имел дело с глубоко вложенными, я использовал это (до того, как в C # 6 появился нулевой оператор объединения). Для меня это довольно ясно, но, может быть, это потому, что я привык к этому, другие люди могут найти это запутанным.

return ( _mywidget ?? new MyWidget() {length = defaultLength}).Length;

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

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

return ( _mywidget ?? myWidget.NullInstance).Length;
andyroschy
источник
0

Магические числа обычно имеют неприятный запах. Если 5 - произвольное число, то лучше разобрать его так, чтобы оно было более четко задокументировано. Исключением, на мой взгляд, будет, если у вас есть набор значений, которые действуют как значения по умолчанию в определенном контексте.

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

Что-то вроде: return (myobj ?? default_obj) .Length

Крис Квинель
источник
0

Свойство length обычно является типом значения, поэтому оно не может быть нулевым, поэтому оператор объединения с нулем здесь не имеет смысла.

Но вы можете сделать это с помощью свойства, которое является ссылочным типом, например: var window = App.Current.MainWindow ?? new Window();

peancor
источник
Пример пытается рассмотреть, что произойдет в вашей ситуации, если Current был нулевым. Ваш пример просто рухнул бы ... но было бы удобно иметь оператора, который может объединяться в нуль где угодно в цепочке неверного направления.
Мир
-1

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

return _obj ?? (_obj = new Class());

поэтому идея заключалась в том, чтобы объединить ??и =присваивание в:

return _obj ??= new Class();

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

Эта идея не моя, но мне очень нравится :)

chakrit
источник
1
Вы жертва плохого примера? Ваш пример можно сократить, так как return _obj ?? new Class();здесь нет необходимости ??=.
Мэтт Эллен
@Matt Эллен, вы ошиблись. Назначение предназначено . Я предлагаю другую альтернативу. Это не совсем то, что просил ОП, но я верю, что это будет так же полезно.
Чакрит
2
зачем вам присваивание, если вы собираетесь вернуть значение?
Мэтт Эллен
ленивая инициализация?
чакрит