三元条件下的隐式转换问题

时间:2011-11-16 17:02:34

标签: c# compiler-errors implicit-conversion

  

可能重复:
  Conditional operator cannot cast implicitly?
  Why does null need an explicit type cast here?

我进行了搜索,但没有找到解释为何发生以下情况的良好解释 我有两个具有共同接口的类,我尝试使用三元运算符初始化此接口类型的实例,如下所示但是无法编译错误“无法确定条件表达式的类型,因为之间没有隐式转换'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类型,我并不是真的想要进行转换(隐式或显式)。我确定这可能是一个静态语言编译问题,但我想了解发生了什么。

4 个答案:

答案 0 :(得分:35)

这是C#的两个特征汇合的结果。

首先,C#永远不会“魔法化”为你的类型。如果C#必须从给定的一组类型中确定“最佳”类型,它总是选择您给出的类型之一。它永远不会说“你给我的类型都不是最好的类型;因为你给我的选择都很糟糕,我会选择一些你没有给我选择的随机事物。”

第二个是C#从里面外面的原因。我们没有说“哦,我看到你试图将条件运算符结果分配给ILogger;让我确保两个分支都能正常工作。”相反的情况发生了:C#说“让我确定两个分支返回的最佳类型,并验证最佳类型是否可以转换为目标类型。”

第二条规则是明智的,因为目标类型可能是我们想要确定的。当你说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种可能性,直到找到兼容的对?问题变得越来越糟。

我们以这种方式原因的lambdas,因此涉及lambda的重载决策在C#中至少是NP-HARD。那里那很糟糕;我们宁愿不为编译器添加更多NP-HARD问题来解决。

您也可以在语言的其他位置看到第一条规则。例如,如果你说:ILogger[] loggers = new[] { consoleLogger, suppressLogger };你会得到类似的错误;推断的数组元素类型必须是给定的类型表达式的最佳类型。如果没有可以从中确定最佳类型,我们不会尝试找到您未提供的类型。

类型推断也是如此。如果你说:

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

然后T不会被推断为ILogger;这将是一个错误。 T被推断为提供的参数类型中的最佳类型,其中没有最佳类型。

有关此设计决策如何影响条件运算符行为的更多详细信息,请参阅my series of articles on that topic

如果您对为什么“从外到内”的重载分辨率为NP-HARD感兴趣,请参阅this article

答案 1 :(得分:11)

你可以这样做:

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

当你有一个像condition ? a : b这样的表达式时,必须有一个从a类型到b类型的隐式转换,反之亦然,否则编译器可以不确定表达式的类型。在您的情况下,SuppressLoggerConsoleLogger ...

之间没有转化

(有关详细信息,请参阅C#4语言规范中的第7.14节)

答案 2 :(得分:3)

问题是语句的右侧是在不查看分配给它的变量类型的情况下进行评估的。

编译器无法查看

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

并确定返回类型应该是什么,因为它们之间没有隐式转换。它不寻找共同的祖先,即使它确实如此,它怎么知道选择哪一个。

答案 3 :(得分:2)

每次将一种类型的变量更改为另一种类型的变量时,都是转换。将类的实例分配给除该类之外的任何类型的变量都需要转换。这句话:

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();

因为第三级操作员只会非常努力地弄清楚你想要的结果类型。它基本上是这样做的:

  1. 如果操作数2和3的类型相同,则第三级运算符返回该类型并跳过其余步骤。
  2. 如果操作数2可以隐式转换为与操作数3相同的类型,则可能返回该类型。
  3. 如果操作数3可以隐式转换为与操作数2相同的类型,则可能返回该类型。
  4. 如果#2和#3都为真,或者#2或#3都不为真,则会产生错误。
  5. 否则,它返回#2或#3中的任何一个的类型。
  6. 特别是,它不会开始搜索它所知道的关于寻找“最小公分母”类型的所有类型,例如共同的接口。此外,对第三运算符进行求值,并且其返回类型确定了独立您将结果存储到的变量类型。这是一个两步过程:

    1. 确定?:表达式的类型并计算它。
    2. 将#1的结果存储到变量中,根据需要执行任何隐式转换。
    3. 如果这是您需要的,那么对您的一个或两个操作数进行类型转换是执行此操作的正确方法。