Я знаю, что стандартный способ использования нулевой оператор объединения в 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
Так что, учитывая, что о нулевом операторе слияния даже мало кто знает ...
Вы использовали ?? для чего-нибудь еще?
Необходим ли ??, или вы должны просто использовать тернарный оператор (который
большинство знакомо)





Is ?? necessary, or should you just use the ternary operator (that most are familiar with)
Вы должны использовать то, что лучше всего выражает ваши намерения. Поскольку является является нулевым оператором объединения, используй это.
С другой стороны, поскольку он настолько специализирован, я не думаю, что он может найти другое применение. Я бы предпочел соответствующую перегрузку оператора ||, как это делают другие языки. Это было бы более экономно в языковом дизайне. Но хорошо …
Во-первых, это намного проще, чем стандартный тернарный оператор:
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"];
Я просто использовал его сегодня, чтобы заменить простой блок IF, который я написал до того, как узнал о тернарном или нулевом операторе слияния. Ветви true и false исходного оператора IF вызывали один и тот же метод, заменяя один из его аргументов другим значением, если определенный вход имеет значение NULL. С оператором объединения с нулевым значением это один вызов. Это действительно эффективно, когда у вас есть метод, требующий двух или более таких замен!
Я использовал его как однострочник с отложенной загрузкой:
public MyClass LazyProp
{
get { return lazyField ?? (lazyField = new MyClass()); }
}
Удобочитаемый? Решайте сами.
Хммм, вы нашли контрпример к тому, «зачем кому-то использовать его в качестве обфусцированного IF» ... который на самом деле очень удобочитаем для меня.
Это мое основное использование Null Coalescing.
Возможно, мне чего-то не хватает (в основном я использую Java), но разве там нет состояния гонки?
@Justin K - состояние гонки возникает только в том случае, если несколько потоков обращаются к свойству LazyProp одного и того же объекта. Это легко исправить с помощью блокировки, если требуется потокобезопасность каждого экземпляра. Очевидно, что в этом примере это не требуется.
@ Джеффри: Если бы это было ясно, я бы не стал задавать этот вопрос. :) Когда я увидел этот пример, я сразу подумал об одноэлементном члене, и, поскольку я выполняю большую часть своего кодирования в многопоточной среде ... Но, да, если мы предполагаем, что код правильный, ничего лишнего не нужно.
Для состояния гонки не обязательно быть синглтоном. Просто общий экземпляр класса, который содержит LazyProp, и несколько потоков, которые обращаются к LazyProp. Lazy <T> - лучший способ делать такие вещи, и по умолчанию он потокобезопасен (вы можете изменить потокобезопасность Lazy <T>).
Здорово! Считайте меня человеком, который не знал об операторе объединения с нулевым значением - это довольно изящная штука.
Я считаю, что это намного легче читать, чем тернарный оператор.
Первое, что приходит на ум, где я мог бы его использовать, - это хранить все мои параметры по умолчанию в одном месте.
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
}
Я нашел его полезным двумя "немного странными" способами:
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
Я использовал ?? в своей реализации IDataErrorInfo:
public string Error
{
get
{
return this["Name"] ?? this["Address"] ?? this["Phone"];
}
}
public string this[string columnName]
{
get { ... }
}
Если какое-либо отдельное свойство находится в состоянии «ошибки», я получаю эту ошибку, а в противном случае - null. Это действительно хорошо работает.
Интересно. Вы используете «это» как свойство. Я никогда этого не делал.
Да, это часть того, как работает IDataErrorInfo. Обычно этот синтаксис полезен только для классов коллекций.
Вы сохраняете сообщения об ошибках в this["Name"], this["Address"] и т. д. ??
Еще одно преимущество состоит в том, что тернарному оператору требуется двойная оценка или временная переменная.
Рассмотрим это, например:
string result = MyMethod() ?? "default value";
в то время как с тернарным оператором у вас остается либо:
string result = (MyMethod () != null ? MyMethod () : "default value");
который дважды вызывает MyMethod, или:
string methodResult = MyMethod ();
string result = (methodResult != null ? methodResult : "default value");
В любом случае, оператор объединения с нулевым значением чище и, я думаю, более эффективен.
+1. Это одна из основных причин, почему мне нравится оператор объединения с нулевым значением. Это особенно полезно, когда вызов MyMethod() имеет какие-либо побочные эффекты.
Если MyMethod() не имеет никаких эффектов, кроме возврата значения, компилятор знает, что не следует вызывать его дважды, поэтому в большинстве случаев вам действительно не нужно беспокоиться об эффективности.
Это также делает вещи более читаемыми, IMHO, когда MyMethod() представляет собой последовательность объектов с точками. Пример: myObject.getThing().getSecondThing().getThirdThing()
@TinyTimZamboni, у вас есть ссылка на такое поведение компилятора?
@KubaWyrostek Я не знаю, как работает компилятор C#, но у меня есть некоторый опыт в теории статических компиляторов с llvm. Есть несколько подходов, которые компилятор может использовать для оптимизации такого вызова. Глобальная нумерация значений заметит, что два вызова MyMethod идентичны в этом контексте, если предположить, что MyMethod является чистой функцией. Другой вариант - автоматическая мемоизация или просто закрытие функции в кеше. С другой стороны: en.wikipedia.org/wiki/Global_value_numbering
MSDN на C# утверждает, что оцениваются и условие, и значение выигрыша, поэтому мне было интересно, следует ли спецификация C# этому утверждению. Для меня это не просто вопрос оптимизации. Два вызова MyMethod() не являются потокобезопасными: возвращаемое значение может различаться между вызовами, хотя сам метод не имеет побочных эффектов.
Единственная проблема заключается в том, что оператор объединения с нулевым значением не обнаруживает пустые строки.
Т.е.
string result1 = string.empty ?? "dead code!";
string result2 = null ?? "coalesced!";
result1 = "" result2 = coalesced!
В настоящее время я пытаюсь переопределить ?? оператор, чтобы обойти это. Конечно, было бы удобно, если бы это было встроено в фреймворк.
Вы можете сделать это с помощью методов расширения, но я согласен, это было бы хорошим дополнением к коду и очень полезным в веб-контексте.
Ага, это частый сценарий ... есть даже специальный метод String.IsNullOrEmpty (string) ...
«оператор объединения с нулевым значением не обнаруживает пустые строки». Ну, это оператор объединения нулевой, а не оператор объединения nullOrEmpty. И лично я презираю смешивание нулевых и пустых значений в языках, которые различают эти два значения, что делает взаимодействие с вещами, которые не очень раздражают. И я немного навязчиво-компульсивен, поэтому меня раздражает, когда языки / реализации в любом случае не различают их, даже если я понимаю причину (как в [большинстве реализаций?] SQL).
?? не может быть перегружен: msdn.microsoft.com/en-us/library/8edha89s(v=vs.100).aspx - хотя было бы неплохо иметь перегружаемый. Я использую комбинацию: s1.Nullify() ?? s2.Nullify(), где string Nullify(this s) возвращает null в случаях, когда строка пуста.
Единственная проблема? Я просто обнаружил, что хочу ?? = и нашел эту ветку, когда смотрел, есть ли способ сделать это. (Ситуация: первый проход загрузил случаи исключений, теперь я хочу вернуться и загрузить значения по умолчанию во все, что еще не было загружено.)
Is ?? necessary, or should you just use the ternary operator (that most are familiar with)
На самом деле, мой опыт показывает, что слишком мало людей знакомы с тернарным оператором (или, точнее, с оператором условный; ?: является «троичным» в том же смысле, что || является двоичным или + либо унарным, либо двоичным; однако это случается. быть единственным тернарным оператором на многих языках), поэтому, по крайней мере, в этом ограниченном примере ваша инструкция не работает прямо здесь.
Кроме того, как упоминалось ранее, существует одна серьезная ситуация, когда оператор объединения с нулевым значением очень полезен, и это когда вычисляемое выражение вообще имеет какие-либо побочные эффекты. В этом случае вы не могу используете условный оператор без (а) введения временной переменной или (б) изменения фактической логики приложения. (b) явно не подходит ни при каких обстоятельствах, и, хотя это личное предпочтение, я не люблю загромождать область объявления множеством посторонних, даже если кратковременных, переменных, поэтому (a) тоже отсутствует в этом конкретный сценарий.
Конечно, если вам нужно выполнить несколько проверок результата, условный оператор или набор блоков if, вероятно, являются инструментом для работы. Но для простого «если это null, используйте это, иначе используйте это», оператор объединения с нулевым значением ?? идеально подходит.
Очень поздний комментарий от меня, но приятно видеть, что кто-то покрывает, что тернарный оператор - это оператор с тремя аргументами (из которых теперь более одного в C#).
Самым большим преимуществом, которое я нахожу для оператора ??, является то, что вы можете легко преобразовать типы значений, допускающие значение 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, который я обычно использую.
KeyValuePair - это тип значения в платформе .NET, поэтому доступ к любому из его свойств никогда не вызовет исключение нулевой ссылки. msdn.microsoft.com/en-us/library/5tbh8a42(v=vs.110).aspx
Вы можете использовать нулевой оператор объединения, чтобы немного упростить обработку случая, когда необязательный параметр не установлен:
public void Method(Arg arg = null)
{
arg = arg ?? Arg.Default;
...
Было бы здорово, если бы эту строчку можно было записать как arg ?= Arg.Default?
Мне нравится использовать нулевой оператор объединения для ленивой загрузки определенных свойств.
Очень простой (и надуманный) пример, чтобы проиллюстрировать мою точку зрения:
public class StackOverflow
{
private IEnumerable<string> _definitions;
public IEnumerable<string> Definitions
{
get
{
return _definitions ?? (
_definitions = new List<string>
{
"definition 1",
"definition 2",
"definition 3"
}
);
}
}
}
На самом деле Resharper предложит это как рефакторинг для «традиционной» отложенной загрузки.
Еще одна вещь, которую следует учитывать, заключается в том, что оператор 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, как и должно быть.
Это заслуживает большего количества голосов. Я ооочень много раз читал, что ?? - это эквивалент для ?:.
Правильное указание на то, что геттер вызывается дважды. Но в этом примере я бы счел плохим паттерном дизайна иметь такой вводящий в заблуждение геттер, который фактически вносит изменения в объект.
Одна вещь, которую я много делал в последнее время, - это использование нулевого объединения для резервных копий на 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";
Это немного странный вариант использования, но у меня был метод, в котором объект IDisposable потенциально передается в качестве аргумента (и, следовательно, удаляется родителем), но он также может быть нулевым (и поэтому должен быть создан и удален в местный метод)
Чтобы использовать его, код выглядел как
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);
[...]
}
Я использовал это так:
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;
}
Цепочка - большой плюс для оператора, убирает кучу избыточных IF.