有什么理由说明为什么C#中的三元有限?

时间:2009-11-03 19:07:56

标签: c# ternary-operator

失败:

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

成功:

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

第一个语句中的错误是:

  

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

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

编辑:上面的示例很简单,是的,但是有一些例子可以说非常有用:

int? subscriptionID; // comes in as a parameter

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

4 个答案:

答案 0 :(得分:17)

使用:

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

问题是条件运算符的返回类型不能无歧义地确定。也就是说,在int和string之间,没有最佳选择。编译器将始终使用true表达式的类型,并在必要时隐式转换false表达式。

修改 在你的第二个例子中:

int? subscriptionID; // comes in as a parameter

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

<强> PS:
这不称为“三元运营商”。 三元运算符,但它被称为“条件运算符”。

答案 1 :(得分:15)

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

首先,希望尽可能多的表达式具有可以仅从表达式的内容确定的明确类型。由于几个原因,这是可取的。例如:它使构建IntelliSense引擎变得更加容易。键入x.M(some-expression.并且IntelliSense需要能够分析 some-expression ,确定其类型,并在IntelliSense知道x.M引用的方法之前生成下拉列表。 IntelliSense无法知道x.M所指的是肯定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

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

第三,C#的一个微妙但一贯应用的设计原则是“不要通过魔法生成类型”。当给出一个表达式列表时,我们必须从中确定一个类型,我们确定的类型总是在列表中。我们永远不会为一种新型装扮,并为您选择它;你得到的类型总是你给我们选择的类型。如果你说要在一组类型中找到最佳类型,我们会找到最佳类型IN类型。在集合{int,string}中,没有最常见的类型,例如“Animal,Turtle,Mammal,Wallaby”。此设计决策适用于条件运算符,类型推断统一场景,隐式类型数组类型的推断,等等。

这个设计决策的原因在于,它使普通人更容易计算出在必须确定最佳类型的任何给定情况下编译器将要做什么;如果你知道那种类型就在那里,盯着你看,那将会被选中,那么找出将要发生的事情要容易得多。

它还避免了在发生冲突时我们必须制定很多关于什么是一组类型的最佳常见类型的复杂规则。假设你有类型{Foo,Bar},其中两个类都实现了IBlah,并且这两个类都继承自Baz。哪个是最好的常见类型,IBlah,它们都实现了,或Baz,两者都延伸?我们不想回答这个问题;我们想完全避免它。

最后,我注意到C#编译器实际上在某些模糊的情况下确定了类型错误的类型。我的第一篇文章就在这里:

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

事实上,编译器做得恰到好处并且规范是错误的,这是有争议的;在我看来,实现设计比规范设计更好。

无论如何,这只是设计三元运算符这一特定方面的几个原因。这里还有其他细微之处,例如,CLR验证程序如何确定给定的一组分支路径是否保证在所有可能的路径中在堆栈上保留正确的类型。详细讨论会让我走得更远。

答案 2 :(得分:2)

为什么特征X这种方式通常是一个非常难以回答的问题。回答实际行为要容易得多。

我的教育猜测为什么。允许条件运算符简洁明了地使用布尔表达式来选择2个相关值。它们必须相关,因为它们在一个位置使用。如果用户改为选择2个不相关的值,那么代码中可能会有一个微妙的拼写错误/错误,编译器最好提醒他们,而不是隐式地转换为对象。这可能是他们没想到的。

答案 3 :(得分:0)

“int”是基本类型,而不是对象,而“string”被认为是“原始对象”。当你执行类似“object o = 1”的操作时,你实际上将“int”装入“Int32”。这是关于拳击的文章的链接:

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

一般情况下,由于性能损失难以追踪,应避免装箱。

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

声明: 对象o =((1 == 2)?1:“test”);

编译器:

  1. '((1 == 2)?1:“test”)'中“1”和“test”的类型是什么?他们匹配吗?
  2. #1的最终类型是否与'object o'的赋值运算符类型匹配?
  3. 由于编译器在#1完成之前不评估#2,因此失败。