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

C# 三元条件下的隐式转换问题,c#,compiler-errors,implicit-conversion,C#,Compiler Errors,Implicit Conversion,可能重复: 我已经搜索过了,但没有找到以下情况发生的好解释。 我有两个类,它们有一个共同的接口,我尝试使用如下三元运算符初始化此接口类型的实例,但编译失败,错误为“无法确定条件表达式的类型,因为'xxx.Class1'和'xxx.Class2'之间没有隐式转换”: public ConsoleLogger : ILogger { .... } public SuppressLogger : ILogger { .... } static void Main(string[] args

可能重复:

我已经搜索过了,但没有找到以下情况发生的好解释。
我有两个类,它们有一个共同的接口,我尝试使用如下三元运算符初始化此接口类型的实例,但编译失败,错误为“无法确定条件表达式的类型,因为'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();
}
如果我显式地将第一个条件强制转换到我的接口中,这将起作用:

   ILogger logger = suppressLogging ? ((ILogger)new SuppressLogger()) : new ConsoleLogger();
显然,我总能做到这一点:

   ILogger logger;
   if (suppressLogging)
   {
       logger = new SuppressLogger();
   }
   else
   {
       logger = new ConsoleLogger();
   }
备选方案很好,但我无法完全理解为什么第一个选项会因隐式转换错误而失败,因为在我看来,这两个类都属于ILogger类型,我并不真正希望进行转换(隐式或显式)。我确信这可能是一个静态语言编译问题,但我想了解发生了什么。

您可以这样做:

ILogger logger = suppressLogging ? (ILogger)(new SuppressLogger()) : (ILogger)(new ConsoleLogger());
当您有一个类似于
条件?a:b
的表达式时,必须有一个从
a
类型到
b
类型的隐式转换,或者反过来,否则编译器无法确定表达式的类型。在您的情况下,
SuppressLogger
控制台之间没有转换记录器


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

问题在于,在计算语句右侧时,没有查看分配给它的变量的类型

编译器无法查看

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

然后决定返回类型应该是什么,因为它们之间没有隐式转换。它不寻找共同的祖先,即使找到了,它又如何知道要选择哪一个。

当您将一种类型的变量更改为另一种类型的变量时,这就是转换。将类的实例分配给任何类型的变量该类以外的类型需要转换。此语句:

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的结果存储到变量中,根据需要执行任何隐式转换

  • 如果您需要的话,对一个或两个操作数进行类型转换是执行此操作的正确方法。

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

    首先,C#永远不会为你“变魔术”。如果C#必须从给定的一组类型中确定“最佳”类型,它总是从您提供的类型中选择一种。它从来没有说“你给我的类型都不是最好的类型;因为你给我的选择都不好,我将随机挑选一些你没有给我选择的东西。”

    第二个是C#从内到外的原因。我们不会说“哦,我看到您试图将条件运算符结果分配给ILogger;让我确保两个分支都工作。”相反的情况是:C表示“让我确定两个分支返回的最佳类型,并验证最佳类型是否可转换为目标类型。”

    第二条规则是合理的,因为目标类型可能就是我们试图确定的类型。当你说D=b时?c:a很清楚目标类型是什么。但是假设您正在调用
    M(b?c:a)
    ?可能有100个不同的重载M,每个重载的形式参数类型不同!我们必须确定参数的类型,然后丢弃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进行了推理,因此涉及lambdas的重载解析在C#中至少是NP-HARD的。这很糟糕;我们不希望为编译器添加更多的NP难问题来解决

    您也可以在语言的其他地方看到第一条规则的作用。考试
    void M<T>(T t1, T t2) { ... }
    ...
    M(consoleLogger, suppressLogger);