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

Неявная проблема преобразования в тройном состоянии

Возможный дубликат:
Условный оператор нельзя использовать неявно?
Почему для нулевого значения требуется явно заданный тип?

У меня был поиск, и я не нашел хорошего объяснения, почему происходит следующее. У меня есть два класса, которые имеют общий интерфейс, и я попытался инициализировать экземпляр этого типа интерфейса, используя трёхмерный оператор, как показано ниже, но это не скомпилируется с ошибкой "Тип условного выражения не может быть определен, потому что нет никакого неявного преобразования между 'xxx.Class1' и 'xxx.Class2':

public ConsoleLogger : ILogger  { .... }

public SuppressLogger : ILogger  { .... }

static void Main(string[] args)
{
   .....
   // The following creates the compile error
   ILogger logger = suppressLogging ? new SuppressLogger() : new ConsoleLogger();
}

Это работает, если я явно применил первый conditioin к моему интерфейсу:

   ILogger logger = suppressLogging ? ((ILogger)new SuppressLogger()) : new ConsoleLogger();

и, очевидно, я всегда могу это сделать:

   ILogger logger;
   if (suppressLogging)
   {
       logger = new SuppressLogger();
   }
   else
   {
       logger = new ConsoleLogger();
   }

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

4b9b3361

Ответ 1

Это является следствием слияния двух характеристик С#.

Во-первых, С# никогда не "магия" типа для вас. Если С# должен определить "лучший" тип из заданного набора типов, он всегда выбирает один из типов, которые вы ему дали. Он никогда не говорит: "ни один из типов, которые вы мне дали, - это лучший тип, так как выбор, который вы мне дали, плох, я собираюсь выбрать какую-то случайную вещь, которую вы не дали мне на выбор".

Во-вторых, из-за того, что С# изнутри наружу. Мы не говорим "О, я вижу, вы пытаетесь присвоить результат условного оператора ILogger, позвольте мне убедиться, что обе ветки работают". Происходит обратное: С# говорит: "Позвольте мне определить лучший тип, возвращаемый обею ветками, и убедитесь, что лучший тип конвертируется в целевой тип".

Второе правило разумно, потому что тип цели может быть тем, что мы пытаемся определить. Когда вы говорите D d = b ? c : a;, ясно, какой тип цели. Но предположим, что вы вместо этого набрали M(b?c:a)? Может быть сто разных перегрузок M каждый с другим типом для формального параметра! Мы должны определить, каков тип аргумента, а затем отбросить перегрузки M, которые неприменимы, поскольку тип аргумента несовместим с формальным типом параметра; мы не идем другим путем.

Подумайте, что произойдет, если мы идем в другую сторону:

M1( b1 ? M2( b3 ? M4( ) : M5 ( ) ) : M6 ( b7 ? M8() : M9() ) );

Предположим, что существует сто перегрузок, каждый из M1, M2 и M6. Чем ты занимаешься? Вы говорите, хорошо, если это M1 (Foo), то M2 (...) и M6 (...) должны быть оба конвертируемыми в Foo. Они? Пусть узнают. Что такое перегрузка M2? Есть сотни возможностей. Посмотрим, будет ли каждая из них конвертируемой из возвращаемого типа M4 и M5... ОК, мы пробовали все это, поэтому мы нашли M2, который работает. Как насчет M6? Что, если "лучший" M2, который мы находим, несовместим с "лучшим" M6? Должны ли мы отступать и продолжать перепробовать все возможности 100 x 100, пока не найдем совместимую пару? Проблема только ухудшается и ухудшается.

Мы так рассуждаем для лямбда, и в результате разрешение перегрузки с участием лямбда не менее NP-HARD в С#. Это плохо. мы предпочли бы не добавлять больше проблем NP-HARD для решения компилятора.

Вы можете увидеть первое правило в действии и в другом месте на этом языке. Например, если вы сказали: ILogger[] loggers = new[] { consoleLogger, suppressLogger };, вы получите аналогичную ошибку; тип выбранного массива должен быть лучшим типом введенных выражений. Если из них нельзя определить лучший тип, мы не пытаемся найти тип, который вы нам не дали.

То же самое происходит в выводах типа. Если вы сказали:

void M<T>(T t1, T t2) { ... }
...
M(consoleLogger, suppressLogger);

Тогда T не будет считаться ILogger; это будет ошибкой. T считается лучшим типом среди поставляемых типов аргументов, и среди них нет лучшего типа.

Подробнее о том, как это дизайнерское решение влияет на поведение условного оператора, см. мою серию статей по этой теме.

Если вас интересует, почему разрешение перегрузки, которое работает "извне внутрь", это NP-HARD, см. эту статью.

Ответ 2

Вы можете сделать это:

ILogger logger = suppressLogging ? (ILogger)(new SuppressLogger()) : (ILogger)(new ConsoleLogger());

Если у вас есть выражение типа condition ? a : b, должно быть неявное преобразование из типа a в тип b или наоборот, иначе компилятор не сможет определить тип выражение. В вашем случае нет преобразования между SuppressLogger и ConsoleLogger...

(подробности см. в разделе 7.14 в спецификациях языка С# 4)

Ответ 3

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

Невозможно просмотреть компилятор

suppressLogging ? new SuppressLogger() : new ConsoleLogger();

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

Ответ 4

Каждый раз, когда вы меняете переменную одного типа в переменную другого типа, это преобразование. Присвоение экземпляра класса переменной любого типа, кроме этого класса, требует преобразования. Это утверждение:

ILogger a = new ConsoleLogger();

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

ILogger a = new ConsoleLogger();
ILogger b = suppress ? new SuppressLogger() : a;

потому что существует неявное преобразование между SuppressLogger и ILogger. Однако это не сработает:

ILogger c = suppress ? new SuppressLogger() : new ConsoleLogger();

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

  • Если типы операндов 2 и 3 одинаковы, третичный оператор возвращает этот тип и пропускает остальные шаги.
  • Если операнд 2 может быть неявно преобразован в тот же тип, что и операнд 3, он может вернуть этот тип.
  • Если операнд 3 может быть неявно преобразован в тот же тип, что и операнд 2, он может вернуть этот тип.
  • Если оба # 2 и # 3 являются истинными, либо ни # 2, ни # 3 не являются истинными, он генерирует ошибку.
  • В противном случае он возвращает тип для того, что было в # 2 или # 3. Это правда.

В частности, он не будет запускать поиск по всем типам, которые он знает, для поиска типа "наименьшего общего знаменателя", такого как общий интерфейс. Кроме того, оценивается третичный оператор, и его тип возврата сжимается, независимый тип переменной, в которую вы храните результат. Это двухэтапный процесс:

  • Определите тип выражения: выражение и вычислите его.
  • Сохраните результат # 1 в переменной, выполнив любые неявные преобразования по мере необходимости.

Указание одного или обоих ваших операндов - правильный способ выполнить эту операцию, если это необходимо.