Почему этот код недействителен в C #?

Следующий код не компилируется:

string foo = "bar";
Object o = foo == null ? DBNull.Value : foo;

Я получаю: Ошибка 1 Тип условного выражения не может быть определен, потому что не существует неявного преобразования между System.DBNull и string

Чтобы исправить это, я должен сделать что-то вроде этого:

string foo = "bar";
Object o = foo == null ? DBNull.Value : (Object)foo;

Это приведение кажется бессмысленным, поскольку это определенно законно:

string foo = "bar";
Object o = foo == null ? "gork" : foo;

Мне кажется, что когда тройные ветки имеют разные типы, компилятор не будет автоматически упаковывать значения в объект типа ... но когда они одного типа, автобоксирование выполняется автоматически.

На мой взгляд, первое утверждение должно быть законным ...

Может ли кто-нибудь описать, почему компилятор не позволяет этого и почему разработчики C # решили это сделать? Я считаю, что это законно в Java ... Хотя я не проверял это.

Спасибо.

РЕДАКТИРОВАТЬ: Я прошу понять, почему Java и C # обрабатывают это по-разному, что происходит под сценами в C #, которые делают это недействительным. Я знаю, как использовать троичный код, и не ищу «лучшего способа» кодирования примеров. Я понимаю правила тернарности в C #, но хочу знать, ПОЧЕМУ ...

РЕДАКТИРОВАТЬ (Джон Скит): удален тег "autoboxing", поскольку в этом вопросе бокс не используется.


person mmattax    schedule 14.10.2008    source источник
comment
Будет ли проще отлаживать, если использовать более эффективный способ тестирования DBNulls, как здесь? : stackoverflow.com/questions/26809/   -  person Pascal Paradis    schedule 14.10.2008
comment
Другие ответы хорошо это объясняют. Еще один лакомый кусочек: бокс здесь не участвовал бы, даже если бы он компилировался. String и DBNull являются ссылочными (классовыми) типами, а не типами значений, поэтому строку можно сохранить в объектной переменной без упаковки.   -  person Neil    schedule 14.10.2008
comment
Вопрос добавлен в C # Tag Wiki   -  person Anthony Pegram    schedule 28.07.2011
comment
srsl, а почему про int не подумал? = (ххх)? null: int; вариант?? Я имею в виду, что это практическая и распространенная проблема, так какого черта мне писать ... (int?) Null: int; это безумие!   -  person Greeed    schedule 22.11.2012


Ответы (3)


Компилятор требует, чтобы типы второго и третьего операндов были одинаковыми или чтобы один из них неявно преобразовывался в другой. В вашем случае это типы DBNull и string, ни один из которых не может быть неявно преобразован в другой. Приведение любого из них к объекту решает эту проблему.

РЕДАКТИРОВАТЬ: Похоже, это действительно законно в Java. Как это работает, что делать, когда дело доходит до перегрузки метода, я не уверен ... Я только что посмотрел на JLS, и крайне неясно, что это за тип условного выражения, когда есть две несовместимые ссылки задействованные типы. Способ работы C # иногда может быть более раздражающим, но он более ясен, IMO.

Соответствующий раздел спецификации C # 3.0 - 7.13, условный оператор:

Второй и третий операнды оператора?: Управляют типом условного выражения. Пусть X и Y - типы второго и третьего операндов. Потом,

  • Если X и Y одного типа, то это тип условного
  • В противном случае, если существует неявное преобразование (§6.1) из X в Y, но не из Y в X, тогда Y является типом условного выражения.
  • В противном случае, если существует неявное преобразование (§6.1) из Y в X, но не из X в Y, то X является типом условного выражения.
  • В противном случае невозможно определить тип выражения и возникает ошибка времени компиляции.
person Jon Skeet    schedule 14.10.2008

DBNull.Value возвращает тип DBNull.

Вы хотите, чтобы тип был string.

Хотя string может быть null, но не может быть DBNull.

В вашем коде оператор справа от равенства выполняется перед присвоением объекту.

В основном, если вы используете:

[condition] ? true value : false value;

В .Net параметры true и false должны быть неявно преобразованы в один и тот же тип до того, как вы их назначите.

Это результат того, как C # работает с безопасностью типов. Например, верно следующее:

string item = "item";

var test = item != null ? item : "BLANK";

C # 3 не поддерживает динамические типы, так что же такое тест? В C # каждое присваивание также является оператором с возвращаемым значением, поэтому, хотя конструкция var является новой в C # 3, оператор справа от равенства всегда должен преобразовываться в один тип.

В C # 4 и выше вы можете явно поддерживать динамические типы, но я не думаю, что здесь это помогает.

person Keith    schedule 14.10.2008
comment
Вы не поняли вопроса. Мне нужен объект типа, а не строка. Я знаю, как работает троичный. Я спрашиваю, почему компилятор не может автоматически объединить два разных типа в троичный объект. - person mmattax; 14.10.2008
comment
Я думаю, что он дал правильный ответ, особенно эта часть: в .Net как истинные, так и ложные параметры должны быть одного типа, прежде чем вы их назначите. Это результат того, как C # работает с безопасностью типов. - person Evan Teran; 14.10.2008
comment
@Keith: На самом деле они не обязательно должны быть одного и того же типа. Только один тип неявно преобразуется в другой. @mmattax: Здесь вообще нет бокса. Ни DBNull, ни String не являются типами значений. - person Jon Skeet; 14.10.2008
comment
Упаковка действительно происходит, когда типы значений преобразуются в тип объекта. и я не спрашиваю о правилах ... Я хочу знать, почему это произошло ... - person mmattax; 14.10.2008
comment
mmattax: Типы значений не задействованы, поэтому упаковки нет. Это помогает быть точным при использовании терминологии. (Он также не называется тернарным оператором - это условный оператор. Он бывает a тернарным оператором - и единственным в C # - но это не его имя.) - person Jon Skeet; 14.10.2008
comment
@Keith - но я хочу разрешить только один тип: объект, поэтому не должно быть задействованных динамических типов. - person mmattax; 14.10.2008
comment
Спасибо, Джон, за разъяснения. mmattax - в .Net строки являются неизменяемыми ссылочными типами - они не упаковываются. Я использовал ключевое слово implicit var только для того, чтобы проиллюстрировать суть дела. - person Keith; 14.10.2008

Кстати, ваш код - это особый случай, в котором вообще не нужно использовать условный оператор. Вместо этого более уместен нулевой оператор объединения (но по-прежнему требует приведения):

object result = (object)foo ?? DBNull.Value;
person Konrad Rudolph    schedule 14.10.2008