9

失败:

object o = ((1==2) ? 1 : "test");

成功:

object o;
if (1 == 2)
{
    o = 1;
}
else
{
    o = "test";
}

第一条语句中的错误是:

无法确定条件表达式的类型,因为 'int' 和 'string' 之间没有隐式转换。

为什么需要,我将这些值分配给对象类型的变量。

编辑:上面的例子是微不足道的,是的,但有些例子会很有帮助:

int? subscriptionID; // comes in as a parameter

EntityParameter p1 = new EntityParameter("SubscriptionID", DbType.Int32)
{
    Value = ((subscriptionID == null) ? DBNull.Value : subscriptionID),
}
4

4 回答 4

17

采用:

object o = ((1==2) ? (object)1 : "test");

问题是条件运算符的返回类型无法明确确定。也就是说,在int和string之间,没有最好的选择。编译器将始终使用真表达式的类型,并在必要时隐式转换假表达式。

编辑: 在你的第二个例子中:

int? subscriptionID; // comes in as a parameter

EntityParameter p1 = new EntityParameter("SubscriptionID", DbType.Int32)
{
    Value = subscriptionID.HasValue ? (object)subscriptionID : DBNull.Value,
}

PS:
那不叫“三元算子”。它一个三元运算符,但它被称为“条件运算符”。

于 2009-11-03T19:09:38.923 回答
15

尽管其他答案是正确的,但从它们做出真实和相关陈述的意义上说,这里还有一些语言设计的微妙点尚未表达出来。许多不同的因素促成了条件运算符的当前设计。

首先,希望尽可能多的表达式具有可以仅根据表达式的内容确定的明确类型。出于几个原因,这是合乎需要的。例如:它使构建 IntelliSense 引擎变得更加容易。您键入x.M(some-expression.,IntelliSense 需要能够分析some-expression,确定其类型,并在 IntelliSense 知道 xM 指的是什么方法之前生成一个下拉列表。IntelliSense 在看到所有参数之前无法确定 xM 指的是什么,如果 M 已重载,但您甚至还没有输入第一个参数。

其次,我们更喜欢类型信息“从内到外”流动,因为正是我刚才提到的场景:重载解析。考虑以下:

void M(object x) {}
void M(int x) {}
void M(string x) {}
...
M(b ? 1 : "hello");

这应该怎么做?它应该调用对象重载吗?它是否应该有时调用字符串重载,有时调用 int 重载?如果你有另一个超载怎么办,比如说M(IComparable x)- 你什么时候选择它?

当类型信息“双向流动”时,事情变得非常复杂。说“我将这个东西分配给对象类型的变量,因此编译器应该知道选择对象作为类型是可以的”并没有洗牌;通常情况下,我们不知道您要分配的变量的类型,因为这就是我们正在尝试找出的过程。重载解析正是从参数类型中计算出参数类型的过程,参数类型是您分配参数的变量。如果参数的类型取决于它们被分配到的类型,那么我们的推理就有循环性。

对于 lambda 表达式,类型信息确实“双向流动”;有效地实施这一点花了我一年的大部分时间。我写了一系列文章,描述了设计和实现编译器的一些困难,该编译器可以根据可能使用表达式的上下文来分析类型信息流入复杂表达式的情况;第一部分在这里:

http://blogs.msdn.com/ericlippert/archive/2007/01/10/lambda-expressions-vs-anonymous-methods-part-one.aspx

你可能会说“好吧,我明白为什么我分配给对象的事实不能被编译器安全地使用,我明白为什么表达式必须具有明确的类型,但为什么不是类型表达式对象,因为 int 和 string 都可以转换为对象?” 这让我想到了第三点:

第三,C# 的微妙但一贯应用的设计原则之一是“不要通过魔法产生类型”。当给定一个我们必须从中确定类型的表达式列表时,我们确定的类型总是在某个列表中。我们从不创造新的类型并为您选择;您获得的类型始终是您让我们选择的类型。如果你说在一组类型中找到最好的类型,我们在该组类型中找到最好的类型。在集合 {int, string} 中,没有最好的常见类型,例如“Animal, Turtle, Mammal, Wallaby”。此设计决策适用于条件运算符、类型推理统一场景、隐式类型数组类型的推理等。

这种设计决策的原因是,在必须确定最佳类型的任何给定情况下,它使普通人更容易计算出编译器将要做什么。如果您知道将要选择一种就在那儿,盯着您的类型,那么很容易弄清楚将要发生的事情。

它还避免了我们必须制定许多复杂的规则,当有冲突时,一组类型的最佳通用类型是什么。假设您有类型 {Foo, Bar},其中两个类都实现了 IBlah,并且两个类都继承自 Baz。哪一个是最好的通用类型,IBlah,两者都实现,或 Baz,两者都扩展?我们不想回答这个问题;我们想完全避免它。

最后,我注意到 C# 编译器实际上在某些晦涩的情况下对类型的确定存在细微的错误。我的第一篇文章在这里:

http://blogs.msdn.com/ericlippert/archive/2006/05/24/type-in​​ference-woes-part-one.aspx

有争议的是,实际上编译器做对了,而规范是错误的;在我看来,实现设计比规范的设计更好。

无论如何,这只是设计三元运算符这一特殊方面的几个原因。这里还有其他一些微妙之处,例如,CLR 验证器如何确定给定的分支路径集是否保证在所有可能路径中的堆栈上都保留正确的类型。详细讨论这一点将把我带到相当远的地方。

于 2009-11-04T07:54:22.090 回答
2

为什么以这种方式使用功能 X 通常是一个很难回答的问题。回答实际行为要容易得多。

我有根据的猜测为什么。允许条件运算符简洁明了地使用布尔表达式在 2 个相关值之间进行选择。它们必须是相关的,因为它们在单个位置使用。如果用户改为选择 2 个不相关的值,则可能代码中有一个微妙的错字/错误,编译器最好提醒他们注意这一点,而不是隐式转换为对象。这可能是他们没有预料到的。

于 2009-11-03T19:16:35.207 回答
0

“int”是一个原始类型,而不是一个对象,而“string”更多地被认为是一个“原始对象”。当您执行“object o = 1”之类的操作时,实际上是将“int”装箱为“Int32”。这是一篇关于拳击的文章的链接:

http://msdn.microsoft.com/en-us/magazine/cc301569.aspx

通常,由于难以追踪的性能损失,应避免装箱。

当您使用三元表达式时,编译器根本不会查看赋值变量来确定最终类型是什么。要将您的原始语句分解为编译器正在执行的操作:

语句:object o = ((1==2) ? 1 : "test");

编译器:

  1. '((1==2) ? 1 : "test")' 中的 "1" 和 "test" 的类型是什么?他们匹配吗?
  2. #1 中的最终类型是否与“object o”的赋值运算符类型匹配?

由于编译器在 #1 完成之前不会评估 #2,因此它会失败。

于 2009-11-03T20:25:45.073 回答