Неявная проблема преобразования в тройном состоянии
Возможный дубликат:
Условный оператор нельзя использовать неявно?
Почему для нулевого значения требуется явно заданный тип?
У меня был поиск, и я не нашел хорошего объяснения, почему происходит следующее.
У меня есть два класса, которые имеют общий интерфейс, и я попытался инициализировать экземпляр этого типа интерфейса, используя трёхмерный оператор, как показано ниже, но это не скомпилируется с ошибкой "Тип условного выражения не может быть определен, потому что нет никакого неявного преобразования между '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, и я действительно не ищу сделать преобразование ( неявный или явный). Я уверен, что это, вероятно, проблема статической языковой компиляции, но я хотел бы понять, что происходит.
Ответы
Ответ 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 в переменной, выполнив любые неявные преобразования по мере необходимости.
Указание одного или обоих ваших операндов - правильный способ выполнить эту операцию, если это необходимо.