Подтвердить что ты не робот

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

У меня есть эти методы расширения и тип перечисления:

public static bool IsOneOf<T>(this T thing, params T[] things)
{
    return things.Contains(thing);
}

public static bool IsOneOf<T>(this T? thing, params T[] things) where T : struct
{
    return thing.HasValue && things.Contains(thing.Value);
}

public enum Color { Red, Green, Blue }

Первый if ниже компилируется; вторая не делает:

 if ((x.Y?.Color).IsOneOf(Color.Red, Color.Green))
 ;
 if (x.Y?.Color.IsOneOf(Color.Red, Color.Green))
 ;

Они зависят только от дополнительного набора круглых скобок. Почему я должен это делать?

Сначала я подозревал, что он выполнял двойное неявное преобразование с bool? до bool, а затем обратно на bool?, но когда я удаляю первый метод расширения, он жалуется, что нет никакого неявного перевода из bool до bool?. Затем я проверил ИЛ и не было отливок. Декомпиляция обратно на С# приводит к тому, что выглядит следующим образом:

if (!(y != null ? new Color?(y.Color) : new Color?()).IsOneOf<Color>(new Color[2]
{
    Color.Red,
    Color.Green
}));

который подходит для версии CLR, в которой я запущен, и чего я ожидаю. Я не ожидал, что x.Y?.Color.IsOneOf(Color.Red, Color.Green) не компилируется.

Что происходит? Это просто способ реализации языка, для которого требуется ()?

Обновление

Вот экранная надпись, показывающая ошибку в контексте. Это становится еще более запутанным для меня. Ошибка действительно имеет смысл; но то, что нет (на мой взгляд, сейчас), почему строка 18 не будет иметь одинаковую проблему.

введите описание изображения здесь

4b9b3361

Ответ 1

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

Рассмотрим следующие примеры:

class B { bool c; }
class A { B b; }
...

A a;
var output = a?.b.c; // infers bool?, throws NPE if (a != null && a.b == null)
// roughly translates to
// var output = (a == null) ? null : a.b.c;

а

A a;
var output = (a?.b).c; // infers bool, throws NPE if (a == null || a.b == null)
// roughly translates to
// var output = ((a == null) ? null : a.b).c;

а затем

A a;
var output = a?.b?.c; // infers bool?, *cannot* throw NPE
// roughly translates to
// var output = (a == null) ? null : (a.b == null) ? null : a.b.c;

// and this is almost the same as
// var output = (a?.b)?.c; // infers bool?, cannot throw NPE
// Only that the second `?.` is forced to evaluate every time.

Цель дизайна здесь, по-видимому, помогает разграничить между a?.b.c и a?.b?.c. Если a равно null, мы ожидаем получить NPE в none. Зачем? Потому что существует нулевое условие непосредственно после a. Таким образом, часть .c должна оцениваться только в том случае, если a не является нулевым, что делает доступ к члену зависимым от предыдущего результата. Добавляя явные скобки, (a?.b).c, мы применяем компилятор, чтобы попытаться получить доступ к .c из (a?.b), независимо от того, что a имеет значение null, что предотвращает "короткое замыкание" всего выражения на null. (используя слова @JamesBuck -s)

В вашем случае x.Y?.Color.IsOneOf(Color.Red, Color.Green) похож на a?.b.c. Он вызовет функцию с сигнатурой bool IsOneOf(Color red) (поэтому перегрузка, где параметр не может быть нулевым, и я разделил общую часть), только когда x.Y не был null, таким образом, обертывая тип выражения в Nullable, чтобы обрабатывать случай, когда x.Y имеет значение null. И поскольку while оценивает bool? вместо bool, он не может использоваться как тест в выражении if.

Ответ 2

Значение x.Y?.Color.IsOneOf(Color.Red, Color.Green) не зависит от перегрузок вашего метода. ?. псевдооперанды x.Y и Color.IsOneOf(Color.Red, Color.Green), хотя последнее само по себе не является допустимым выражением. Сначала оценивается x.Y. Вызвать результат tmp. Если tmp == null, то все выражение null. Если tmp != null, то все выражение оценивается как tmp.Color.IsOneOf(Color.Red, Color.Green).

Когда IsOneOf является методом экземпляра, это почти всегда поведение, которое вам нужно, и именно поэтому это поведение, которое попадает в спецификацию и в компилятор.

Когда IsOneOf - это метод расширения, как и в вашем примере, то это может быть не поведение, которое вам нужно, но поведение одинаковое, для согласованности и, поскольку имеется доступное обходное решение, вы уже нашли.

Ассоциативность и то, почему текущий результат был выбран, объясняются в потоке на сайте Roslyn CodePlex.

Ответ 3

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

Ответ был как бы смотрел на меня в захвате экрана в моем вопросе

Ошибка преобразования типа

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

В длинном "пунктирном" выражении, содержащем оператор ?., вы должны думать в терминах вещи и ее типа после самой правой точки. В этом случае это метод расширения, который возвращает bool, но использование ?. эффективно "изменяет" его на bool?. Другими словами, думать о самом правом, как о ссылочном типе, который может быть null или Nullable<T>.

Ничего себе, этот меня заставил меня идти.

Ответ 4

Если вы поместите следующую строку, вы увидите, что .net создает динамический тип System.Nullable<UserQuery+Color> для этого нулевого условного выражения внутри круглых скобок.

Console.WriteLine((x.Y?.Color).GetType().ToString());

Если вы не помещаете скобки, он, вероятно, пытается оценить как нормальное выражение, таким образом, ошибку.