来自'??' 的 C# 类型推断(“var”)赋值空合并运算符

Posted

技术标签:

【中文标题】来自\'??\' 的 C# 类型推断(“var”)赋值空合并运算符【英文标题】:C# type inference ("var") assignment from '??' null-coalescing operator来自'??' 的 C# 类型推断(“var”)赋值空合并运算符 【发布时间】:2017-12-13 00:32:33 【问题描述】:

我已经阅读了许多关于 null 合并 ?? 运算符的 SO 问题,但似乎没有一个解决以下具体问题,这既不涉及 nullability (here),运算符优先级(here 和here),尤其是隐式转换(here、here、here 和here)。我还阅读了.NET docs(更多here)并尝试阅读offical spec,但遗憾的是无济于事。


就这样吧。以下两行之间的唯一区别是在第二行中使用var 进行类型推断,而在第一行中使用显式类型Random,但第二行给出了如图所示的错误,而第一行很好。

Random x = new Random() ?? (x = new Random());        // ok

var    y = new Random() ?? (y = new Random());        // CS0841
                        //  ^-------- error here

CS0841:在声明之前不能使用局部变量“y”

第二行究竟是什么让结果变得不确定?

从我上面引用的 hubub 中,我了解到 ?? 运算符左侧为 null 的可能性引入了对其右侧实际实例化类型的运行时确定的依赖。嗯,好吧,我猜,……那是什么意思?也许这个网站上关于?? 运营商的普遍警告应该是某种可怕的警告......

现在归零,我认为var 关键字(与dynamic 非常相反)的全部意义在于它不受这样的运行时考虑的影响,根据定义

换句话说,即使我们采用保守但完全可以防御的规则“永远不要在任何赋值= 运算符之外进行窥视”,因此我们无法从?? 的右侧获得任何有用的信息,然后仅基于左侧,整体结果必须“兼容”Random。也就是说,结果必须是Random或更具体的(派生的)类型;它不能更普遍。因此,根据定义,Random 不应该是推断类型,因为 var 的这种编译时使用?

据我所知,使用运行时考虑破坏var 会破坏其目的。这不正是dynamic 的用途吗?所以我想问题是:

null-coalescing 运算符是我理解 C# 静态(即编译时)类型哲学的唯一和/或罕见的例外吗? 如果是,那么这种设计与这里似乎正在发生的事情(即故意将非确定性引入静态类型推断系统,它以前没有表现出)之间有什么好处或权衡?实现dynamic 就不能不破坏静态类型的纯度吗? 通过向开发人员提供可操作的反馈来实现严格的编译时设计,这难道不是强类型的要点之一吗?为什么var 不能只维持严格保守的政策——总是推断出可以静态推断的最具体类型——同时null-coalescing operator 正在根据来自未来的信息做任何它想做的事情?

【问题讨论】:

"结果必须是 Random 或更具体的(派生的)类型;它不能更一般。" 你反过来用object 替换var 就可以了。跨度> var 不是运行时,而是编译时。 dynamic 是运行时。 dynamic 主要是为了通过 DLR 与动态语言(例如 Python)进行互操作而创建的。 @KennethK。这正是我的观点。你有什么具体意见吗? @BenVoigt 非常感谢;我在您提到的地方修复了错误,另外还修复了一个错误。这很尴尬,因为信息偏序应该是我的主要专业领域之一。 @GlennSlayden:嗯?不,第 1 行是不可推断的。它编译是因为根本没有推理。存在的是一个无用的额外分配,它在它分配给的变量的初始化之前发生(如果评估)。初始化将始终覆盖分配的值。 【参考方案1】:

这不是运行时考虑。

使用var 声明的变量的编译时类型是其初始化程序的静态类型。 ?? 表达式的静态类型是两个操作数的静态类型的公共类型。但是第二个操作数的静态类型是y的静态类型,这个是未知的。因此整个初始化器的静态类型未知,推演失败。

确实存在初始化一致的类型,但使用 C# 推理规则无法找到它们。

【讨论】:

事实上在这种情况下objectRandom 将是y 的一致类型,所以它是模棱两可的。实际上,可以通过使用两个类来获得任意数量的一致类型,每个类都实现任意数量的接口。无论如何,要分析一段永远不应该使用的代码,需要付出很多努力。【参考方案2】:

当您使用var 时,类型是在编译时计算出来的。因此,当你写这个时:

var    y = new Random() ?? (y = new Random()); 

编译器在编译时无法确定y的类型是什么,因此开始大喊大叫——??的左侧是否为空,将在运行时确定。

一个更好的例子是:

public interface IA  void Do(); 
public class A : IA  ... 
public class B : IA  ... 

A a = null;
var something = a ?? new B(); 

something 的类型应该是什么:IAAB

【讨论】:

这是在乞求问题,为什么它不能确定它什么时候和第一个一样明显。 @novaterata 阅读我的例子,你就会明白为什么。在发布的问题中,这很明显,但在我提供的示例中,一点也不明显。 @CodingYoshi:对于您的示例,运行时null 是什么并不重要。 var something = new A() ?? new B(); 的错误表明您的示例由于其他原因无法编译,即任何可能存储在 a 中的对象现在或在运行时或永远不可能成为 B。强烈排除它,因为 NET 不允许实例的多重继承(类只能具有一种基类型)。 ?? 确实允许多重继承 接口 可能统一的复杂情况,通常需要在 ?? 的左侧或右侧进行强制转换 @CodingYoshi 我可能夸大了,您的示例可以按原样工作,只需将?? 的任一侧转换为(IA),因为它是一个接口。但这不适用于转换为 (A)(B),因为这些是“强烈排除”的类 @GlennSlayden 是的,只要我们告诉编译器我们期待IA,它就会起作用,这就是我的重点。

以上是关于来自'??' 的 C# 类型推断(“var”)赋值空合并运算符的主要内容,如果未能解决你的问题,请参考以下文章

在 Java / Eclipse 中推断变量类型,如 C# 的“var”

[原创]Scala学习:关于变量(val,var,类型推断)

JDK11 | 第三篇 : 局部变量类型推断

C#中隐式类型本地变量var

LayaBox---TypeScript---JavaScript文件类型检查

Kotlin 变量和常量的声明(var和val)