【问题标题】:Are there any good reasons why ternaries in C# are limited?C# 中的三元组受到限制有什么好的理由吗?
【发布时间】:2010-12-12 18:33:37
【问题描述】:

失败:

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),
}

【问题讨论】:

标签: c# ternary-operator


【解决方案1】:

使用:

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:
那不叫“三元运算符”。它三元运算符,但它被称为“条件运算符”。

【讨论】:

  • 详细来说,1 是一种原始类型,因此您必须在分配之前将其强制转换为对象。
  • C#中基元是否继承对象?
  • @Wells 他们从 ValueType 继承,并且因为你将它转换为一个对象,所以那里正在进行一些拳击。
  • 三元条件的类型始终是真值的强类型。 false 值被隐式转换为该类型。
【解决方案2】:

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

首先,希望尽可能多的表达式具有可以仅从表达式的内容确定的明确类型。出于几个原因,这是合乎需要的。例如:它使构建 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# 的微妙但一贯应用的设计原则之一是“不要通过魔法产生类型”。当给定一个我们必须从中确定类型的表达式列表时,我们确定的类型总是在列表中的某个地方。我们从不创造新的类型并为您选择;您获得的类型始终是您让我们选择的类型。如果你说要在一组类型中找到最好的类型,我们在该组类型中找到最好的类型。在集合 {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 验证器如何确定给定的分支路径集是否保证在所有可能路径中的堆栈上保留正确的类型。详细讨论这一点会让我走得更远。

【讨论】:

  • 谢谢埃里克。我刚刚阅读了第一段,想知道智能感知是否可以在您键入时识别出正确的重载?例如,如果我有一个名为 Perform 的方法,它只能采用类型、字符串、int、float、Size 等的 1 个参数,智能感知是否会使用我使用的类型滚动到正确的重载信息工具提示?因此,如果我使用 Perform (myString),它会立即显示 Perform (string) 的工具提示吗?现在它只有在参数数量发生变化时才会滚动。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

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

    一般来说,由于难以追踪的性能损失,应该避免装箱。

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

    声明: 对象 o = ((1==2) ? 1 : "测试");

    编译器:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-14
      • 1970-01-01
      • 1970-01-01
      • 2014-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多