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


164

Я знаю, що стандартним способом використання оператора злиття Null в 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

Отже, зважаючи на те, що менше хтось навіть знає про оператор з’єднання нуля ...

  • Ви використовували ??для чогось іншого?

  • Чи ??потрібно, або ви просто використовуєте потрійного оператора (з яким більшість знайомих)

Відповіді:


216

Ну, по-перше, ланцюг набагато простіше, ніж стандартний потрійний:

string anybody = parm1 ?? localDefault ?? globalDefault;

vs.

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

Він також добре працює, якщо нульовий об'єкт не є змінною:

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

vs.

string anybody = (Parameters["Name"] != null ? Parameters["Name"] 
                 : (Settings["Name"] != null) ? Settings["Name"]
                 :  GlobalSetting["Name"];

12
Ланцюг - це великий плюс для оператора, він знімає купу зайвих IF
chakrit

1
Я щойно використав його сьогодні для заміни простого блоку IF, про який я писав, перш ніж знав про те, чи потрійний, або нульовий оператор злиття. Справжні та помилкові гілки вихідного оператора IF називають один і той же метод, замінюючи один з його аргументів іншим значенням, якщо певний вхід NULL. З оператором нульового згортання це один виклик. Це дійсно потужно, коли у вас є метод, який потребує двох і більше таких підстановок!
Девід А. Грей

177

Я використовував це як ледачий навантажувач:

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

Читання? Вирішіть самі.


6
Гммм, ви знайшли контрприклад "чому хтось хотів би використовувати його як затуманений АБ" ... це насправді дуже читабельно для мене.
Годеке

6
Можливо, мені щось не вистачає (я в основному використовую Java), але чи немає там умови гонки?
Джастін К

9
@Justin K - Існує лише умова перегонів, якщо декілька потоків мають доступ до властивості LazyProp одного і того ж об'єкта. Це легко піддається фіксації за допомогою замка, якщо потрібна обшивка для кожного примірника. Зрозуміло, що в цьому прикладі це не потрібно.
Джефрі Л Уітлідж

5
@Jeffrey: Якби це було зрозуміло, я б не задавав питання. :) Коли я побачив цей приклад, я одразу подумав про однотонного члена, і оскільки мені трапляється більша частина кодування в багатопотоковому середовищі ... Але так, якщо ми вважаємо, що код правильний, нічого зайвого не потрібно.
Джастін К

7
Для того, щоб мати умову гонки, це не повинно бути синглтоном. Просто спільний екземпляр класу, який містить LazyProp, та декілька потоків, які мають доступ до LazyProp. Ледачий <T> - це кращий спосіб робити подібні речі, і за замовчуванням безпечний для потоків (ви можете змінити безпеку потоку Lazy <T>).
Niall Connaughton

52

Я вважаю його корисним двома "злегка дивними" способами:

  • В якості альтернативи наявності outпараметра під час запису TryParseпідпрограм (тобто повернення нульового значення, якщо аналіз не вдається)
  • Як порівняння "не знаю" для порівнянь

Останній потребує трохи більше інформації. Як правило, коли ви створюєте порівняння з декількома елементами, вам потрібно зрозуміти, чи дає перша частина порівняння (наприклад, вік) остаточну відповідь, а наступна частина (наприклад, назва), лише якщо перша частина не допомогла. Використання оператора null coalescing означає, що ви можете написати досить прості порівняння (чи то для впорядкування, чи рівності). Наприклад, використовуючи пару допоміжних класів у 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, а також деякі розширення, які роблять подібні речі ще простішими - але вони все ще акуратні.

Те ж саме можна зробити для перевірки еталонної рівності (або нікчемності) на початку впровадження рівняння.


Мені подобається те, що ви робили з 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

Він також зберігає речі, які читаються IMHO, коли MyMethod()це ланцюгова послідовність пунктирних об'єктів. Вихід:myObject.getThing().getSecondThing().getThirdThing()
xdhmoore

@TinyTimZamboni, чи є у вас посилання на таку поведінку компілятора?
Kuba Wyrostek

@KubaWyrostek Я не знаю конкретних функцій компілятора C #, але у мене є досвід роботи зі статичною теорією компілятора з llvm. Компілятор може скористатися низкою підходів, щоб оптимізувати такий виклик. Global Value нумерація помітить, що MyMethodв цьому контексті два виклики однакові, припускаючи, що MyMethodце функція Pure. Інший варіант - автоматичне запам'ятовування або просто закриття функції в кеші. З іншого боку: 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;

getter буде викликатися двічі, і countзмінна буде дорівнює 2, і якщо ви використовуєте:

var b = a.Prop ?? 0

countзмінна буде дорівнює 1, як це повинно бути.


4
Це заслуговує більшої кількості результатів. Я прочитав дуууже багато разів , що ??є еквівалентом для ?:.
Kuba Wyrostek

1
Дійсна точка щодо дзвонителя, який викликається двічі. Але цей приклад я вважаю поганим дизайнером, щоб мати такий оманливо названий геттер, який фактично вносить зміни в об'єкт.
Лінас

15

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

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, тому доступ до будь-яких його властивостей ніколи не викидає нульове виняткове посилання. msdn.microsoft.com/en-us/library/5tbh8a42(v=vs.110).aspx
Ryan

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"
                }
            );
        }
    } 
}

Resharper насправді запропонує це як рефактор для "традиційного" ледачого навантаження.
arichards

5

Чи ?? або потрібно просто скористатися термінальним оператором (з яким більшість знайомих)

Власне, мій досвід полягає в тому, що все занадто мало людей знайомі з потрійним оператором (або, правильніше, умовним оператором; він ?:є "потрійним" у тому ж сенсі, що ||є двійковим або+ є одинарним або двійковим; проте, проте, це є лише термінальний оператор багатьма мовами), тож принаймні в цьому обмеженому зразку ваше повідомлення не вдається.

Також, як згадувалося раніше, існує одна головна ситуація, коли оператор зведення нуля є дуже корисним, і це завжди, коли вираження, що оцінюється, має взагалі будь-які побічні ефекти. У такому випадку ви не можете використовувати умовний оператор без (a) введення тимчасової змінної або (b) зміни фактичної логіки програми. (b) явно не підходить за будь-яких обставин, і, хоча це особисте перевагу, мені не подобається захаращувати область декларації безліччю сторонніх, навіть якщо короткочасні змінні, тому (a) занадто не в цьому конкретний сценарій.

Звичайно, якщо вам потрібно зробити кілька перевірок результату, умовного оператора або набору if блоків, ймовірно, є інструментом для виконання завдання. Але для простого "якщо це недійсне значення, використовуйте це, інакше використовуйте його", оператор злиття нуля ??є ідеальним.


Дуже пізній коментар від мене - але приємно бачити, що хтось прикриває, що потрійний оператор - це оператор з трьома аргументами (яких зараз у C # більше).
Стів Кідд

5

Одне, що я багато роблю останнім часом, - це використовувати null coalescing для створення резервних копій 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";

4

Єдина проблема в тому, що оператор null-coalesce не виявляє порожніх рядків.


тобто

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

string result2 = null ?? "coalesced!";

ВИХІД:

result1 = ""

result2 = coalesced!

Я зараз розглядаю перевагу ?? оператор, щоб обійти це. Звичайно, було б корисно, щоб це було вбудовано в Рамки.

Думки?


Це можна зробити за допомогою методів розширення, але я згоден, це було б приємним доповненням до коду та дуже корисним у веб-контексті.
Армстронгест

Так, це частий сценарій ... є навіть спеціальний метод String.IsNullOrEmpty (рядок) ...
Макс Галкін

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

Чи ?? або потрібно просто скористатися термінальним оператором (з яким більшість знайомих)

Вам слід використовувати те, що найкраще виражає ваші наміри. Оскільки є нульовий оператор з'єднання, використовуйте його .

З іншого боку, оскільки він настільки спеціалізований, я не думаю, що він має інші можливості. Я вважаю за краще відповідне перевантаження ||оператора, як це роблять інші мови. Це було б більш сприятливим у мовному дизайні. Але добре ...


3

Класно! Порахуйте мене як когось, хто не знав про оператора зведення нуля - це досить чудові речі.

Мені це набагато легше читати, ніж потрійний оператор.

Перше місце, яке мені спадає на думку, де я можу це використовувати, - це зберігати всі мої параметри за замовчуванням в одному місці.

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
}

2

Трохи дивний випадок використання, але у мене був метод, коли 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);
    [...]
}

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; 
            }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.