Есть ли какие-либо веские причины, по которым троичные числа в C # ограничены?
-
13-09-2019 - |
Вопрос
Терпит неудачу:
object o = ((1==2) ? 1 : "test");
Преуспевает:
object o;
if (1 == 2)
{
o = 1;
}
else
{
o = "test";
}
Ошибка в первом утверждении заключается в следующем:
Тип условного выражения не может быть определен, поскольку нет неявного преобразования между 'int' и 'string'.
Хотя зачем это нужно, я присваиваю эти значения переменной типа object.
Редактировать: Приведенный выше пример тривиален, да, но есть примеры, когда это было бы весьма полезно:
int? subscriptionID; // comes in as a parameter
EntityParameter p1 = new EntityParameter("SubscriptionID", DbType.Int32)
{
Value = ((subscriptionID == null) ? DBNull.Value : subscriptionID),
}
Решение
использование:
object o = ((1==2) ? (object)1 : "test");
Проблема заключается в том, что возвращаемый тип условного оператора не может быть определен однозначно.То есть, между int и string нет лучшего выбора.Компилятор всегда будет использовать тип истинного выражения и при необходимости неявно приводить выражение false.
Редактировать: В вашем втором примере:
int? subscriptionID; // comes in as a parameter
EntityParameter p1 = new EntityParameter("SubscriptionID", DbType.Int32)
{
Value = subscriptionID.HasValue ? (object)subscriptionID : DBNull.Value,
}
PS:
Это не называется "троичным оператором". IT является троичный оператор, но он называется "условным оператором".
Другие советы
Хотя другие ответы таковы правильный, в том смысле, что они делают правдивые и релевантные утверждения, здесь есть некоторые тонкие моменты языкового дизайна, которые еще не были выражены.Множество различных факторов влияют на текущую конструкцию условного оператора.
Во-первых, желательно, чтобы как можно больше выражений имели однозначный тип, который может быть определен исключительно по содержимому выражения.Это желательно по нескольким причинам.Например:это значительно упрощает создание движка IntelliSense.Вы печатаете x.M(some-expression.
и IntelliSense должен уметь анализировать некоторое выражение, определите его тип и создайте выпадающий список ДО того, как IntelliSense узнает, к какому методу относится x.M.IntelliSense не может точно знать, на что ссылается x.M, если M перегружен, пока не увидит все аргументы, но вы еще не ввели даже первый аргумент.
Во-вторых, мы предпочитаем, чтобы информация о типе передавалась "изнутри наружу" именно из-за сценария, о котором я только что упомянул:разрешение перегрузки.Рассмотрим следующее:
void M(object x) {}
void M(int x) {}
void M(string x) {}
...
M(b ? 1 : "hello");
Что это должно делать?Должно ли это вызывать перегрузку объекта?Должен ли он иногда вызывать перегрузку строки, а иногда вызывать перегрузку int?Что, если бы у вас была еще одна перегрузка, скажем M(IComparable x)
-- когда ты его выбираешь?
Все становится очень сложно, когда информация о типе "течет в обе стороны".Высказывание "Я присваиваю эту вещь переменной типа object, поэтому компилятор должен знать, что можно выбирать object в качестве типа" не стирает;часто бывает так, что мы не знаем тип переменной, которой вы присваиваете значение, потому что это то, что мы пытаемся выяснить в процессе.Разрешение перегрузки - это именно процесс определения типов параметров, которые являются переменными, которым вы присваиваете аргументы, на основе типов аргументов.Если типы аргументов зависят от типов, которым они присваиваются, то в наших рассуждениях наблюдается цикличность.
Информация о типе "передается в обе стороны" для лямбда-выражений;эффективная реализация этой идеи заняла у меня большую часть года.Я написал длинную серию статей, описывающих некоторые трудности при разработке и реализации компилятора, который может выполнять анализ, при котором информация о типах преобразуется в сложные выражения на основе контекста, в котором возможно используется выражение;часть первая находится здесь:
Вы могли бы сказать: "ну, хорошо, я понимаю, почему тот факт, что я присваиваю object , не может быть безопасно использован компилятором, и я понимаю, почему для выражения необходимо иметь однозначный тип, но почему тип выражения не является object, поскольку и int, и string преобразуются в object?" Это подводит меня к моему третьему пункту:
В-третьих, один из тонких, но последовательно применяемых принципов проектирования C # - "не создавайте типы с помощью магии".Когда дается список выражений, из которых мы должны определить тип, тип, который мы определяем, всегда где-то есть в списке.Мы никогда не придумываем что-то новое и не выбираем его за вас;тип, который вы получаете, всегда тот, который вы предоставили нам на выбор.Если вы говорите найти лучший тип в наборе типов, мы находим лучший тип В этом наборе типов.В наборе {int, string} нет наилучшего общего типа, который есть, скажем, в "Animal, Turtle, Mammal, Wallaby".Это проектное решение применимо к условному оператору, к сценариям унификации вывода типов, к выводу неявно типизированных типов массивов и так далее.
Причина такого конструкторского решения заключается в том, что обычным людям легче понять, что компилятор собирается делать в любой заданной ситуации, когда должен быть определен наилучший тип;если вы знаете, что будет выбран типаж, который находится прямо здесь, глядя вам в лицо, тогда намного легче понять, что произойдет.
Это также избавляет нас от необходимости разрабатывать множество сложных правил о том, какой тип является наиболее распространенным из набора типов, когда возникают конфликты.Предположим, у вас есть типы {Foo, Bar}, где оба класса реализуют IBlah, и оба класса наследуются от Baz.Какой наиболее распространенный тип, IBlah, который реализуют оба, или Baz, который оба расширяют?Мы не хотим отвечать на этот вопрос;мы хотим полностью избежать этого.
Наконец, я отмечаю, что компилятор C # на самом деле слегка ошибается в определении типов в некоторых неясных случаях.Моя первая статья об этом находится здесь:
http://blogs.msdn.com/ericlippert/archive/2006/05/24/type-inference-woes-part-one.aspx
Можно утверждать, что на самом деле компилятор делает это правильно, а спецификация неправильная;дизайн реализации, на мой взгляд, лучше, чем стандартный дизайн.
В любом случае, это всего лишь несколько причин для разработки этого конкретного аспекта троичного оператора.Здесь есть и другие тонкости, например, как верификатор CLR определяет, гарантированно ли данный набор ветвящихся путей оставляет правильный тип в стеке во всех возможных путях.Подробное обсуждение этого вопроса завело бы меня довольно далеко.
Почему функция X работает именно так, часто бывает очень сложно ответить на вопрос.Гораздо проще ответить на фактическое поведение.
Мое обоснованное предположение, почему.Условному оператору разрешено лаконично и лаконично использовать логическое выражение для выбора между двумя связанными значениями.Они должны быть связаны, поскольку используются в одном месте.Если вместо этого пользователь выбирает 2 несвязанных значения, возможно, в коде есть тонкая опечатка/ошибка, и компилятору лучше предупредить их об этом, а не неявно приводить к объекту.Чего они, возможно, не ожидали.
«int» — это примитивный тип, а не объект, а «строка» считается скорее «примитивным объектом».Когда вы делаете что-то вроде «object o = 1», вы фактически помещаете «int» в «Int32».Вот ссылка на статью о боксе:
http://msdn.microsoft.com/en-us/magazine/cc301569.aspx
Как правило, следует избегать бокса из-за потери производительности, которую трудно отследить.
Когда вы используете троичное выражение, компилятор вообще не смотрит на переменную присваивания, чтобы определить окончательный тип.Чтобы разбить исходный оператор на то, что делает компилятор:
Заявление:объект о = ((1==2) ?1:"тест");
Компилятор:
- Каковы типы «1» и «тест» в '((1==2) ?1:"тест")'?Они совпадают?
- Соответствует ли последний тип из № 1 типу оператора присваивания для объекта o?
Поскольку компилятор не вычисляет #2, пока не будет выполнен #1, он терпит неудачу.