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

нулевой коалесцирующий оператор является правильным ассоциативным, что означает выражение вида

Сначала

второй третий

оценивается как

Сначала

(второй третий)

Исходя из приведенного выше правила, я думаю, что следующий перевод неверен.

From:

Address contact = user.ContactAddress;
if (contact == null)
{
    contact = order.ShippingAddress;
    if (contact == null)
    {
        contact = user.BillingAddress;
    }
}

To:

Address contact = user.ContactAddress ??
                  order.ShippingAddress ??
                  user.BillingAddress;

Вместо этого, я думаю, что следующее правильно (пожалуйста, поправьте меня, если я ошибаюсь)

Address contact = (user.ContactAddress ?? order.ShippingAddress) ??
                   user.BillingAddress;

Ответ 1

Спектр на самом деле является самопротиворечивым на этом.

В разделе 7.13 спецификации С# 4 указано:

Оператор нулевой коалесценции является право-ассоциативным, что означает, что операции группируются справа налево. Например, выражение формы a ?? b ?? c оценивается как a ?? (b ?? c).

С другой стороны, как было указано, 7.3.1 утверждает, что:

За исключением операторов присваивания, все бинарные операторы лево-ассоциативны

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

Я рассмотрю это дальше, пинг Mads и Eric, и добавлю ошибку для соответствующего раздела С# в Depth (который вдохновил этот вопрос).

EDIT: Хорошо, теперь у меня есть пример, где это имеет значение... и нулевой коалесцирующий оператор определенно право-ассоциативный, по крайней мере, в компиляторе MS С# 4. Код:

using System;

public struct Foo
{
    public static implicit operator Bar(Foo input)
    {
        Console.WriteLine("Foo to Bar");
        return new Bar();
    }

    public static implicit operator Baz(Foo input)
    {
        Console.WriteLine("Foo to Baz");
        return new Baz();
    }
}

public struct Bar
{
    public static implicit operator Baz(Bar input)
    {
        Console.WriteLine("Bar to Baz");
        return new Baz();
    }
}

public struct Baz
{
}


class Test
{
    static void Main()
    {
        Foo? x = new Foo();
        Bar? y = new Bar();
        Baz? z = new Baz();

        Console.WriteLine("Unbracketed:");
        Baz? a = x ?? y ?? z;
        Console.WriteLine("Grouped to the left:");
        Baz? b = (x ?? y) ?? z;
        Console.WriteLine("Grouped to the right:");
        Baz? c = x ?? (y ?? z);
    }
}

Вывод:

Unbracketed:
Foo to Baz
Grouped to the left:
Foo to Bar
Foo to Bar
Bar to Baz
Grouped to the right:
Foo to Baz

Другими словами,

x ?? y ?? z

ведет себя так же, как

x ?? (y ?? z)

но не совпадает с

(x ?? y) ?? z

В настоящее время я не уверен, почему есть два перехода из Foo в Bar при использовании (x ?? y) ?? z - мне нужно проверить это более тщательно...

EDIT: теперь у меня есть еще один вопрос, чтобы охватить двойное преобразование...

Ответ 2

Джон ответ правильный.

Просто чтобы быть ясно: ?? Оператор в С# является ассоциативным справа. Я только что прошел через синтаксический анализатор бинарных операторов и проверил, что парсер обрабатывает ?? как право-ассоциативный.

Как Джон указывает, спецификация говорит и что ?? оператор является ассоциативным справа, и что все бинарные операторы, кроме присваивания, являются ассоциативными слева. Поскольку спецификация противоречит самой себе, очевидно, что только один из них может быть правильным. Я буду изменять спецификации, чтобы сказать что-то вроде:

За исключением простого присваивания, составного присваивания и операторов объединения нулей, все бинарные операторы являются левоассоциативными

ОБНОВЛЕНИЕ: Как отмечено в комментариях, лямбда-оператор => также является ассоциативным справа.

Ответ 3

Я не вижу, как это важно:

(a ?? b) ?? c

и

a ?? (b ?? c)

имеют тот же результат!

Ответ 4

Обе работают как ожидалось и эффективно идентичны, потому что выражения включают простые типы (спасибо @Jon Skeet). Он будет привязан к первому ненулевому слева направо в ваших примерах.

Приоритет (спасибо @Ben Voigt) более интересен при объединении этого оператора с операторами с различным приоритетом:

value = A ?? B ? C : D ?? E;

В принципе ассоциативность выражается по собственному усмотрению с помощью приоритета оператора или через пользовательские подвыражения (круглые скобки).