C# type inference (“var”) assignment from '??' null-coalescing operator
我读过很多关于空合并??运算符的so问题,但它们似乎都没有解决以下具体问题,即既不涉及空性(此处)、运算符优先级(此处和此处),也不涉及隐式转换(此处、此处、此处和此处)。我还阅读了.NET文档(这里有更多),并尝试阅读官方规范,但遗憾的是,这些都没有用。
所以这里是。下面两行之间的唯一区别是在第二行中使用var进行类型推理,而在第一行中使用显式Random,但是第二行给出了如图所示的错误,而第一行则很好。
1 2 3 4
| Random x = new Random () ?? (x = new Random ()); // ok
var y = new Random () ?? (y = new Random ()); // CS0841
// ^-------- error here |
CS0841: Cannot use local variable 'y' before it is declared
第二行到底是什么让结果变得不确定?
从我上面引用的hubub中,我了解到??运算符左侧为null的可能性引入了对其右侧的实际实例化类型的运行时确定的依赖性。嗯,好吧,我想……这意味着什么?也许这个站点上的??操作员发出的警报量应该是某种可怕的警告…
现在归零,我认为var关键字(与dynamic非常明确地相反)的全部要点是,从定义上讲,它不可能接受这样的运行时考虑。
换言之,即使我们采用保守但完全可以防御的规则,"绝不超越任何任务看=操作符",这样我们就不会从??的右侧得到任何有用的信息,那么仅从左侧来看,总的结果必须"与"Random兼容。也就是说,结果必须是Random或更具体(派生)的类型;它不能更通用。因此,根据定义,对于这个编译时使用var的情况,Random不应该是推断类型吗?
据我所知,用运行时的考虑来破坏var很快就破坏了它的目的。这正是dynamic的目的吗?所以我想问题是:
- 对于我对C静态(即编译时)类型哲学的理解,空合并运算符是唯一和/或罕见的例外吗?
- 如果是的话,那么这个设计与这里看起来正在发生的事情之间的好处或折衷是什么,也就是故意将不确定性引入静态类型推理系统,而这个系统以前没有表现出来?在不破坏静态类型的纯度的情况下,是否可以实现dynamic?
- 通过向开发人员提供可操作的反馈,强类型化不是实现编译时设计严格性的主要要点之一吗?为什么var不能保持严格的保守主义政策——总是推断出可以静态推断的最具体的类型——同时,空合并运算符正在根据未来的信息做它想做的任何事情?
- "结果必须是随机的或更具体的(派生的)类型;不能更一般。"你倒过来看,用object替换var会非常好。
- var不是运行时,它是编译时。dynamic是运行时的。dynamic的创建主要是为了通过DLR与动态语言(如python)进行交互。
- @ KennethK。这正是我的观点。你有什么特别的意见吗?
- @本沃伊特非常感谢,我修正了你提到的地方的错误,另外还有一个错误。因为信息部分排序应该是我专业化的主要领域之一,所以这是令人尴尬的。
- 嗯,@benvoigt正在重新考虑…(这种误解实际上是我所在领域的一个众所周知的问题)。我把"更派生"的类型定义为"更具体",所以object是"最一般"的类型,位于概念树的"顶部",其具体性向下。一些逻辑学家把信息的部分顺序颠倒过来。你用那个视图吗?在我的版本中,由于??的左边显然是Random,所以右边必须更派生,不能是object,因为object可能不是Random。哦,或者你认为"右边"指的是任务?
- @格伦斯莱登:你有没有一条规则说,??的左边操作数不会隐式转换为右边操作数的类型?因为很明显扩展版本object z; var tmp = new Random(); if (tmp != null) z = tmp; else z = (z = new Random());没有问题
- 我发现的规则是,右边的类型比左边的类型更通用,在这种情况下,左边的类型会被隐式地向上转换。请参阅github.com/dotnet/csharplang/blob/master/spec/…上的第五个项目符号。
- @好吧,我的观点是,最大化var的编译时实用程序需要采用以下规则:(1)总是假设可以静态计算的最具体类型;(2)从不putting,即使所说的最大努力是object)。既然你已经明确地宣布z为object,我认为var在你的例子中没有工作要做。但是您的评论进一步强调了当前的??规范/行为掩盖了var的设计"意图"否则可能是……我们无法找到任何方法!
- @格伦:这个评论只是针对更一般/更具体的问题,而不是var问题。在任何情况下,我仍然不清楚您希望通过在自己的初始值设定项中使用y来实现什么。这是对更复杂表达式的简化吗?或者你是在考虑(var y = new Random()) ?? (y = new Random());,但这不是语法的工作方式。
- @本沃伊特,我看到了,是的,这似乎是症结所在。引文"…转换为B型,这就变成了结果",显然只能对dynamic有任何意义。无法及时将信息发送回var。所以问题仍然存在,为什么var突然决定完全放弃(致命错误)的可能性?特别是到目前为止,为什么var不能继续预测它所能得到的最佳静态分析,而不是仅仅因为引入了一个模糊的新运算符而突然变成一个偏函数呢?
- @格伦斯莱登:你认为是??导致了这一点吗?但任何类型依赖于操作数类型的运算符的操作方式都完全相同。var y = 1 + (y = 2)将无法推断类型。重载函数调用也是如此。
- @Benvoigt my examples here are minimal and considerably implicated from my actual use cases,which are mostly related to prevessive lock-free programming with EDOCX1>0但这还不足以说明,在原始对比中,没有var的情况下,1线是完全不可逆转的吗?
- @Glennslayden:huh?不,线 351是不可阻挡的。这很容易,因为没有任何推理。What is,is a useless extra assignation that takes place(if evaluated)before the initiatization of the variable it assigns to.初始化将始终超过指定值。
- @Benvoigt ok,fair enough;I hope I didn't cut too much in over-Zealous简化。这是var y = 1 + (y = 2)的性质,没有任何信息可以提供。与此相反,有可操作的推理,即没有最大收获量。我开始得出结论认为,这是由于@Codingyoshi's"yelling"explanation而引起的,而且这在EDOCX1&1的初步假设哲学中代表了一种类别的变化——朝着一条主干(Towards EDOCX1&6),Rather than Maximally Assisting,Compile-Time Dev,a s previously.
- @Glenn:我越来越相信,这里的问题是你最初宣布的精神模式。《宣言》不在《电子商务示范法》1〔7〕的左操作中。操作员仅在初始化器内,而在转动中则是宣言的一部分。在var y =中,这不是操作员,而是宣言的语法的一部分。因此,运算符优先权并不适用于这一点。
- 所以你说子弹点 \ \\\\\\不会有"回到正轨"的人
- @Glenn:Think in terms of recursive descent,for both parsing and type analysis,it'll help.基本上,编译器正在尝试做:Locals.Add("y"); Locals["y"].StaticType = GetStaticType("new Random() ?? (y = new Random())");和最后的Min(GetStaticType("new Random()"), GetStaticType("y = new Random"))评价,这是Min(typeof(Random), Locals["y"].StaticType)。最后,你有EDOCX1〕〔17〕,你看到了,在那之前,你的类型是知道的。
- @Benvoigt,这不是我的精神模型;我承认我们一直在谈论var y = (new Random() ?? (y = new Random()))。我认为,我一直在试图进入的问题是,注意到EDOCX1的右侧〔7〕(我曾试图为讨论的酒精制造一个混乱的时间丢失的原因),从左侧唯一能得到的关于y的信息是什么?
- @Glenn:没有什么可以被输入到左边。运算符取决于运算符的类型。如果你没有两种类型,你就无法应用规则来确定结果类型(在我们讨论过的那些泡沫点的人)。
- 考虑这一点,你希望从int z = f(new Random() ?? new object());得到什么?这是"永远1"、"永远2"还是"有时1,有时2"?
- @Benvoigt yes,exactly,until runtime,according to the(possibly misguided)spec,which is why the"going back in time"is still at issue.这就意味着一种运行时间评价这就成了"一个明确无误或无用的声明,它与var的关系。我同意,如所述,规则是雅林,也许是在传统的正面飞行。考虑:new Random() ?? new Random()As Randomand both new Object() ?? new Random()and the swapped as Object,proving that ??inference goes towards the General
- 我意识到我犯了一个错误。编译器所用的伪代码不是Locals.Add("y"); Locals["y"].StaticType = Min(typeof(Random), Locals["y"].StaticType);,更像EDOCX1〕〔8〕通过KeyNotFound向错误者转换的代码,例外情况是,用一个未申报的变量转换成错误,而不是用EDOC1〔0〕设计的错误。
- @Benvoigt..So given this discoverable"rule",as you suggested Earlier,why isn't varsatified to classify new Object() ?? ...anything-under-the-sunas Objectas Object,without even have to look at the right side?
- @Benvoigt what's jarring is that var爬上这条通往致命编译的道路,Rather than just saying,"OK,then,Objectit is."
- @Glenn:因为"If a is system.Object,the result is system.object"。每个子弹点都需要有B.even if all possible values of B give the same result(which happens when a is system.object).
- 在这类规则中没有短路。
- @Glenn:actually,you partially have a point,because the rule"otherwise,if Aexists and an implicate transformation exists from bto A,the result type is A.""doesn't need bto exist…"这就是不需要b的类型。不幸的是,在C x1 x5
- @Glenn:在最后分析中,这是因为var没有特殊类型的分析。It resues the type analysis that already exists in the compiler for E.G.determining the type of a function argument for purposes of overload resolution.
- @Benvoigt Excellent Catch,which might,I think,explain the bad smell,I was movely noticed from working with ??way too much(C.F.lock-free Interlocked但是,对我来说,这是一个很大的启示,你的第一点是害怕部分命令,也许帮助了最重要的。与直觉相反,这是EDOCX1&6的非常自然的性质,它聚集了它的操作类型(Towards EDOCX1&5),现在我看到它必须是这样。运行条件默示的静态式推理比常见的还要多,我们应该感激——以批判为基础——这是什么
- (@Glenn:EDOCX1的结果类型〕〔6〕(和不涉及EDOCX1〕〔30〕的任何表达式)是静态确定的。而静态测定必须以这样一种方式进行,它对两个分支都是有用的,准确地说,因为分支被捕捉到的分支不知道有多长时间才能影响静态分析。这与C 35;(不象一些拼写语言)致死分支需要的代码相同。部分是近时编译模型,部分是C 35;狭窄。例如,在return; f(new Random());中,呼叫很明显,但必须有效。
这不是运行时的考虑。
使用var声明的变量的编译时类型是其初始值设定项的静态类型。??表达式的静态类型是两个操作数静态类型的常见类型。但第二个操作数的静态类型是y的静态类型,这是未知的。因此,整个初始值设定项的静态类型未知,推导失败。
确实存在初始化一致的类型,但是使用C推理规则找不到它们。
- 事实上,在这种情况下,object和Random对于y来说都是一致的类型,所以这是不明确的。实际上,通过使用两个类(每个类实现任意数量的接口),可以获得任意数量的一致类型。在任何情况下,分析一行永远不应该使用的代码都需要付出很大的努力。
使用var时,类型在编译时计算出来。因此,当你写下:
1
| var y = new Random () ?? (y = new Random ()); |
编译器无法确定y的类型在编译时是什么,因此开始叫喊决定??的左侧是否为空,将在运行时确定。
一个更好的例子是:
1 2 3 4 5 6
| public interface IA { void Do(); }
public class A : IA { ... }
public class B : IA { ... }
A a = null;
var something = a ?? new B (); |
something的类型应该是什么:IA、A或B?
- 这是个问题,为什么它不能确定,当它和第一个一样明显的时候。
- @Novaterata读了我的例子,你就会明白为什么。在发布的问题中,它是显而易见的,但在我提供的示例中,它一点也不明显。
- @Codingyoshi:对于您的例子,我不在乎运行时的null是什么。var something = new A() ?? new B();的错误表明,您的示例编译的原因不同,也就是说,任何可能存储在a中的对象现在、运行时或任何时候都不可能是B的对象,因为net不允许多个实例继承(类只能有一个基类型)。e)。??正确地允许多重继承接口可能统一的复杂情况,通常需要在??的左侧或右侧进行强制转换。
- @codingyoshi我可能已经说过了,你的例子可以通过简单地将??的任一侧转换为(IA)来工作,因为它是一个接口。但这不适用于向(A)或(B)投射,因为这些类是"强烈排除"的类。
- @Glennslayden是的,只要我们告诉编译器我们期待的是IA,这就是我的全部观点,它就会工作。