Уникальные способы использования оператора Null Coalescing [закрыто]

165

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

string nobody = null;
string somebody = "Bob Saget";
string anybody = "";

anybody = nobody   ?? "Mr. T"; // returns Mr. T
anybody = somebody ?? "Mr. T"; // returns "Bob Saget"

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

nobody = null;
anybody = nobody == null ? "Bob Saget" : nobody; // returns Bob Saget

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

  • Вы использовали ??для чего-то еще?

  • Это ??необходимо, или вы должны просто использовать троичный оператор (с которым большинство знакомо)

Armstrongest
источник

Ответы:

216

Ну, во-первых, это гораздо проще, чем стандартная троичная:

string anybody = parm1 ?? localDefault ?? globalDefault;

против

string anyboby = (parm1 != null) ? parm1 
               : ((localDefault != null) ? localDefault 
               : globalDefault);

Это также хорошо работает, если нулевой объект не является переменной:

string anybody = Parameters["Name"] 
              ?? Settings["Name"] 
              ?? GlobalSetting["Name"];

против

string anybody = (Parameters["Name"] != null ? Parameters["Name"] 
                 : (Settings["Name"] != null) ? Settings["Name"]
                 :  GlobalSetting["Name"];
Джеймс Керран
источник
12
Цепочка является большим плюсом для оператора, удаляет кучу избыточных
IF
1
Я просто использовал его сегодня, чтобы заменить простой блок IF, который я написал до того, как узнал о тройном или нулевом операторе слияния. Истинная и ложная ветви исходного оператора IF вызвали один и тот же метод, заменив один из его аргументов другим значением, если определенный вход имеет значение NULL. С нулевым оператором объединения это один вызов. Это действительно мощно, когда у вас есть метод, который требует двух или более таких замен!
Дэвид А. Грей
177

Я использовал его как однорядную ленту загрузки:

public MyClass LazyProp
{
    get { return lazyField ?? (lazyField = new MyClass()); }
}

Удобочитаемый? Решай сам.

Кристиан Либардо
источник
6
Хммм, вы нашли контрпример "почему бы кому-то захотеть использовать его в качестве скрытого ПЧ" ... это на самом деле очень легко читаемо для меня.
Годеке
6
Я мог что-то упустить (я в основном использую Java), но разве там нет условий гонки?
Джастин К
9
@Justin K - существует условие гонки, если несколько потоков обращаются к свойству LazyProp одного и того же объекта. Это легко исправить с помощью блокировки, если требуется защита потока каждого экземпляра. Понятно, что в этом примере это не требуется.
Джеффри Л Уитледж
5
@ Джеффри: Если бы это было ясно, я бы не задавал вопрос. :) Когда я увидел этот пример, я сразу подумал об одноэлементном члене, и поскольку мне приходится выполнять большую часть своего кодирования в многопоточной среде ... Но, да, если мы предположим, что код правильный, ничего лишнего не нужно.
Джастин К
7
Это не должен быть Синглтон, чтобы иметь состояние гонки. Просто общий экземпляр класса, который содержит LazyProp, и несколько потоков, которые обращаются к LazyProp. Lazy <T> - лучший способ сделать это, и он по умолчанию безопасен для потоков (вы можете выбрать для изменения безопасности потоков для Lazy <T>).
Найл Коннотон
52

Я нашел это полезным двумя «немного странными» способами:

  • В качестве альтернативы для outпараметра при записи TryParseподпрограмм (т. Е. Возвращает нулевое значение, если синтаксический анализ не удается)
  • Как «не знаю» представление для сравнения

Последнему нужно немного больше информации. Как правило, когда вы создаете сравнение с несколькими элементами, вам нужно увидеть, дает ли первая часть сравнения (например, возраст) окончательный ответ, а затем следующую часть (например, имя), только если первая часть не помогла. Использование оператора объединения нулей означает, что вы можете писать довольно простые сравнения (для упорядочения или равенства). Например, используя пару вспомогательных классов в MiscUtil :

public int Compare(Person p1, Person p2)
{
    return PartialComparer.Compare(p1.Age, p2.Age)
        ?? PartialComparer.Compare(p1.Name, p2.Name)
        ?? PartialComparer.Compare(p1.Salary, p2.Salary)
        ?? 0;
}

По общему признанию у меня теперь есть ProjectionComparer в MiscUtil, наряду с некоторыми расширениями, которые делают такие вещи еще проще - но это все еще опрятно.

То же самое можно сделать для проверки на референтное равенство (или нулевое значение) в начале реализации Equals.

Джон Скит
источник
Мне нравится то, что вы делали с PartialComparer, но я искал случаи, когда мне нужно было сохранять переменные оцененного выражения. Я не разбираюсь в лямбдах и расширениях, так что вы могли бы увидеть, придерживается ли следующий шаблон подобного паттерна (т.е. работает ли он)? stackoverflow.com/questions/1234263/#1241780
maxwellb
33

Другое преимущество состоит в том, что троичный оператор требует двойной оценки или временной переменной.

Рассмотрим это, например:

string result = MyMethod() ?? "default value";

в то время как с троичным оператором у вас остается либо:

string result = (MyMethod () != null ? MyMethod () : "default value");

который вызывает MyMethod дважды, или:

string methodResult = MyMethod ();
string result = (methodResult != null ? methodResult : "default value");

В любом случае, оператор нулевого слияния чище и, я думаю, более эффективен.


источник
1
+1. Это одна из главных причин, почему мне нравится нулевой оператор объединения. Это особенно полезно, когда у звонка MyMethod()есть побочные эффекты.
CVn
Если MyMethod()за пределами возврата значения нет никаких эффектов, компилятор знает, что его нельзя вызывать дважды, поэтому вам не нужно беспокоиться об эффективности здесь в большинстве случаев.
TinyTimZamboni
Это также делает вещи более читабельными ИМХО, когда MyMethod()это последовательность цепочек пунктирных объектов. Пример:myObject.getThing().getSecondThing().getThirdThing()
xdhmoore
@TinyTimZamboni, у вас есть ссылка на это поведение компилятора?
Куба Уиростек
@KubaWyrostek У меня нет знаний о конкретной работе компилятора C #, но у меня есть некоторый опыт работы со статической теорией компилятора с llvm. Существует несколько подходов, которые компилятор может использовать для оптимизации вызова, подобного этому. Глобальная нумерация значений заметит, что MyMethodв этом контексте два вызова идентичны, предполагая, что MyMethodэто чистая функция. Другой вариант - автоматическая памятка или просто закрытие функции в кеше. С другой стороны: en.wikipedia.org/wiki/Global_value_numbering
TinyTimZamboni
23

Еще одна вещь, которую следует учитывать, это то, что оператор coalesce не вызывает метод get свойства дважды, как это делает троичный.

Таким образом, есть сценарии, где вы не должны использовать троичный, например:

public class A
{
    var count = 0;
    private int? _prop = null;
    public int? Prop
    {
        get 
        {
            ++count;
            return _prop
        }
        set
        {
            _prop = value;
        }
    }
}

Если вы используете:

var a = new A();
var b = a.Prop == null ? 0 : a.Prop;

геттер будет вызван дважды, а countпеременная будет равна 2, и если вы используете:

var b = a.Prop ?? 0

countпеременная будет равна 1, как это должно быть.

Фабио Лима
источник
4
Это заслуживает большего количества голосов. Я прочитал ооочень много раз , что ??является эквивалентом для ?:.
Куба Уиростек
1
Допустимый пункт о геттере, вызываемом дважды. Но в этом примере я бы посчитал, что плохой шаблон дизайна имеет такой вводящий в заблуждение метод get, чтобы фактически вносить изменения в объект.
Линас
15

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

int? test = null;
var result = test ?? 0; // result is int, not int?

Я часто использую это в запросах Linq:

Dictionary<int, int?> PurchaseQuantities;
// PurchaseQuantities populated via ASP .NET MVC form.
var totalPurchased = PurchaseQuantities.Sum(kvp => kvp.Value ?? 0);
// totalPurchased is int, not int?
Райан
источник
Я могу немного опоздать здесь, но второй пример приведёт, если kvp == null. И на самом деле Nullable<T>есть GetValueOrDefaultметод, который я обычно использую.
CompuChip
6
KeyValuePair является типом значения в .NET Framework, поэтому доступ к любому из его свойств никогда не вызовет исключение нулевой ссылки. msdn.microsoft.com/en-us/library/5tbh8a42(v=vs.110).aspx
Райан
9

Я использовал ?? в моей реализации IDataErrorInfo:

public string Error
{
    get
    {
        return this["Name"] ?? this["Address"] ?? this["Phone"];
    }
}

public string this[string columnName]
{
    get { ... }
}

Если какое-либо отдельное свойство находится в состоянии «ошибка», я получаю эту ошибку, в противном случае я получаю ноль. Работает действительно хорошо.

Мэтт Гамильтон
источник
Интересный. Вы используете «это» как свойство. Я никогда этого не делал.
Армстронгест
Да, это часть того, как работает IDataErrorInfo. Обычно этот синтаксис полезен только для классов коллекции.
Мэтт Гамильтон
4
Вы храните сообщения об ошибках this["Name"], this["Address"]и т.д. ??
Андрей
7

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

public void Method(Arg arg = null)
{
    arg = arg ?? Arg.Default;
    ...
Найл Коннотон
источник
Не было бы замечательно, если бы эту строку можно было записать как arg ?= Arg.Default?
Джесси де Вит
6

Мне нравится использовать оператор null coalesce для отложенной загрузки определенных свойств.

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

public class StackOverflow
{
    private IEnumerable<string> _definitions;
    public IEnumerable<string> Definitions
    {
        get
        {
            return _definitions ?? (
                _definitions = new List<string>
                {
                    "definition 1",
                    "definition 2",
                    "definition 3"
                }
            );
        }
    } 
}
mlnyc
источник
Решарпер фактически предложит это как рефакторинг для «традиционной» ленивой нагрузки.
аричардс
5

Является ?? необходимо, или вы просто должны использовать троичный оператор (с которым большинство знакомо)

На самом деле, мой опыт показывает, что слишком немногие знакомы с троичным оператором (или, точнее, условным оператором; он ?:является «троичным» в том же смысле, что ||является двоичным, +либо унарным, либо двоичным; только троичный оператор во многих языках), так что, по крайней мере, в этом ограниченном примере, ваше утверждение терпит неудачу прямо здесь.

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

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

CVn
источник
Очень поздний комментарий от меня - но приятно видеть, что кто-то покрывает, что троичный оператор - это оператор с тремя аргументами (которых в C # сейчас больше, чем один).
Стив Кидд
5

В последнее время я часто использовал одну вещь - использовать объединение нулей для резервного копирования as. Например:

object boxed = 4;
int i = (boxed as int?) ?? 99;

Console.WriteLine(i); // Prints 4

Это также полезно для резервного копирования длинных цепочек ?., каждый из которых может потерпеть неудачу

int result = MyObj?.Prop?.Foo?.Val ?? 4;
string other = (MyObj?.Prop?.Foo?.Name as string)?.ToLower() ?? "not there";
Blue0500
источник
4

Единственная проблема в том, что оператор null-coalesce не обнаруживает пустые строки.


т.е.

string result1 = string.empty ?? "dead code!";

string result2 = null ?? "coalesced!";

ВЫВОД:

result1 = ""

result2 = coalesced!

В настоящее время я смотрю на переопределение ?? Оператор обойти это. Конечно, было бы удобно встроить это в Framework.

Мысли?

Кевин Панко
источник
Вы можете сделать это с помощью методов расширения, но я согласен, это было бы хорошим дополнением к коду и очень полезно в веб-контексте.
Армстронгест
Да, это частый сценарий ... есть даже специальный метод String.IsNullOrEmpty (string) ...
Макс Галкин
12
«оператор null-coalesce не обнаруживает пустые строки». Ну, это оператор null- coalescing, а не оператор nullOrEmpty- coalescing. И лично я презираю смешивание нулевых и пустых значений в языках, которые различают эти два, что делает взаимодействие с вещами, которые не совсем раздражают. И я немного обсессивно-компульсивный, поэтому меня раздражает, когда языки / реализации в любом случае не делают различий между ними, даже если я понимаю причины (как в [большинстве реализаций?] SQL).
JAB
3
??не может быть перегружен: msdn.microsoft.com/en-us/library/8edha89s(v=vs.100).aspx - это было бы здорово, если бы была перегружена. Я использую комбинацию: s1.Nullify() ?? s2.Nullify()где string Nullify(this s)возвращает a nullв случаях, когда строка пуста.
Кит
Единственная проблема? Я просто обнаружил, что хочу ?? = и нашел эту тему, когда смотрел, есть ли способ сделать это. (Ситуация: первый проход загружал случаи исключений, теперь я хочу вернуться назад и загрузить значения по умолчанию во все, что еще не было загружено.)
Лорен Печтел
3

Является ?? необходимо, или вы просто должны использовать троичный оператор (с которым большинство знакомо)

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

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

Конрад Рудольф
источник
3

Прохладно! Считай меня кем-то, кто не знал об операторе слияния null - это довольно изящная штука.

Мне гораздо легче читать, чем троичный оператор.

Первое, что приходит на ум, где я могу использовать это - хранить все мои параметры по умолчанию в одном месте.

public void someMethod( object parm2, ArrayList parm3 )
{ 
  someMethod( null, parm2, parm3 );
}
public void someMethod( string parm1, ArrayList parm3 )
{
  someMethod( parm1, null, parm3 );
}
public void someMethod( string parm1, object parm2, )
{
  someMethod( parm1, parm2, null );
}
public void someMethod( string parm1 )
{
  someMethod( parm1, null, null );
}
public void someMethod( object parm2 )
{
  someMethod( null, parm2, null );
}
public void someMethod( ArrayList parm3 )
{
  someMethod( null, null, parm3 );
}
public void someMethod( string parm1, object parm2, ArrayList parm3 )
{
  // Set your default parameters here rather than scattered through the above function overloads
  parm1 = parm1 ?? "Default User Name";
  parm2 = parm2 ?? GetCurrentUserObj();
  parm3 = parm3 ?? DefaultCustomerList;

  // Do the rest of the stuff here
}
HanClinto
источник
2

Немного странный вариант использования, но у меня был метод, в котором IDisposableобъект потенциально передается как аргумент arg (и, следовательно, располагается родителем), но также может иметь значение null (поэтому его следует создавать и удалять в локальном методе).

Чтобы использовать его, код либо выглядел как

Channel channel;
Authentication authentication;

if (entities == null)
{
    using (entities = Entities.GetEntities())
    {
        channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
        [...]
    }
}
else
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

Но с нулевым слиянием становится намного аккуратнее

using (entities ?? Entities.GetEntities())
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}
PaulG
источник
0

Я использовал так:

for (int i = 0; i < result.Count; i++)
            {
                object[] atom = result[i];

                atom[3] = atom[3] ?? 0;
                atom[4] = atom[4] != null ? "Test" : string.Empty;
                atom[5] = atom[5] ?? "";
                atom[6] = atom[6] ?? "";
                atom[7] = atom[7] ?? "";
                atom[8] = atom[8] ?? "";
                atom[9] = atom[9] ?? "";
                atom[10] = atom[10] ?? "";
                atom[12] = atom[12] ?? false; 
            }
Мухаммед Авайс
источник