使用空合并运算符进行隐式转换
Posted
技术标签:
【中文标题】使用空合并运算符进行隐式转换【英文标题】:Implicit conversion with null-coalescing operator 【发布时间】:2014-02-18 16:12:54 【问题描述】:我发现我的程序有一个奇怪的行为,经过进一步分析,我发现我的 C# 知识或其他地方可能有问题。我相信这是我的错误,但我无法在任何地方找到答案......
public class B
public static implicit operator B(A values)
return null;
public class A
public class Program
static void Main(string[] args)
A a = new A();
B b = a ?? new B();
//b = null ... is it wrong that I expect b to be B() ?
此代码中的变量“b”被评估为 null。我不明白为什么它是空的。
我用谷歌搜索了这个问题 - Implicit casting of Null-Coalescing operator result - 的官方规范。
但是按照这个规范,我找不到“b”为空的原因:(也许我读错了,在这种情况下我为垃圾邮件道歉。
如果 A 存在且不是可空类型或引用类型,则会发生编译时错误。
...不是这样的。
如果 b 是动态表达式,则结果类型是动态的。在运行时,首先评估 a。如果 a 不为 null,则将 a 转换为动态,这将成为结果。否则,对 b 求值,这将成为结果。
...事实并非如此。
否则,如果 A 存在并且是可空类型,并且存在从 b 到 A0 的隐式转换,则结果类型为 A0。在运行时,首先评估 a。如果 a 不为 null,则将 a 解包为类型 A0,这将成为结果。否则,b 被求值并转换为 A0 类型,这就是结果。
...A 存在,不存在从 b 到 A0 的隐式转换。
否则,如果存在 A 并且存在从 b 到 A 的隐式转换,则结果类型为 A。在运行时,首先计算 a。如果 a 不为空,则 a 成为结果。否则,b 被求值并转换为 A 类型,这就是结果。
...A 存在,不存在从 b 到 A 的隐式转换。
否则,如果 b 具有 B 类型并且存在从 a 到 B 的隐式转换,则结果类型为 B。在运行时,首先计算 a。如果 a 不为空,则将 a 解包为 A0 类型(如果 A 存在且可为空)并转换为 B 类型,这将成为结果。否则,b 被评估并成为结果。
...b 具有 B 类型,并且存在从 a 到 B 的隐式转换。 a 被评估为 null。因此,b 应该被评估并且 b 应该是结果。
否则a和b不兼容,会出现编译时错误。 不会发生
请问我有什么遗漏吗?
【问题讨论】:
不应该重写操作符来工作吗? 非常有趣。请注意,如果您将最后一行更改为B b = (B)a ?? new B();
,即您在代码中“显式”编写隐式强制转换,则会有所不同。
@JeppeStigNielsen 你这样做是在强制转换发生在空检查之前,而不是之后。
@Servy 没错。我发现并写在我的答案中。
【参考方案1】:
为什么您希望 null 合并运算符返回 new B()
? a
不为空,因此 a ?? new B()
的计算结果为 a
。
既然我们知道将返回a
,我们需要确定结果的类型(T
)以及是否需要将a
转换为T
。
• 否则,如果 b 具有 B 类型并且存在从 a 到 B,结果类型为 B。 在运行时,首先评估 a。如果一个 不为空,a 是 解包为 A0 类型(如果 A 存在并且可以为空) 并转化为B型,这就是结果。否则,b 是 评估并成为结果。
存在从A
到B
的隐式转换,因此B
是表达式的结果类型。这意味着a
将被隐式转换为B
。并且您的隐式运算符返回 null
。
事实上,如果你写var b = a ?? new B();
(注意var
),你会看到编译器推断B
是表达式返回的类型。
【讨论】:
这并不能解释为什么将声明更改为object b
仍然会产生 null。
@KendallFrey 足够接近它。 ??
仅在第一个和第二个操作数解析为相同类型(或类型的可空和不可空版本)时才有效。第一个操作数a
与null 进行比较,然后转换为B
类型,此时隐式转换将其转换为null
。它根本不需要分配给任何东西。
对于??
运算符,问题本身中引用的规则比您引用的规则更相关(并且更复杂)(通过 Lipperts 博客)对于三元 ?:
运算符。例如,给定变量 bool b = false; short? s = 10; int i = 10;
,表达式 s ?? i
是允许的,而 b ? s : i
则不能编译。这个答案是第一位的,但我认为其他答案更准确。
@JeppeStigNielsen 不错的收获!每当讨论三元和空合并运算符时,我通常会参考该博客文章 - 我倾向于忘记这些规则仅适用于三元运算符(即使两组规则非常相似) .我已经更新了我的答案。【参考方案2】:
否则,如果 b 具有 B 类型并且存在从 a 的隐式转换 对于 B,结果类型为 B。在运行时,首先评估 a。如果一个是 不为空,a 被解包为 A0 类型(如果 A 存在并且可以为空)并且 转换为 B 型,这就是结果。否则,b 是 评估并成为结果。
...b 具有 B 类型,并且存在从 a 到 B 的隐式转换。 a 是 评估为空。因此,b 应该被评估并且 b 应该是 结果。
您对此的解释是错误的。没有说 a
到 B
的转换是在执行 null
检查之前完成的。它指出null
检查是在在转换之前完成的!
你的情况很适合:
如果 a 不为 null,则将 a 解包为 A0 类型(如果 A 存在并且是 可以为空)并转换为B型,这成为 结果。
【讨论】:
【参考方案3】:好吧,规范说(我改为x
和y
以减少混淆):
• 否则,如果 y 的类型为 Y,并且存在从 x 到 Y 的隐式转换,则结果类型为 Y。在运行时,首先计算 x。如果 x 不为空,则 x 被解包为 X0 类型(如果 X 存在并且可以为空)并转换为 Y 类型,这就是结果。否则,对 y 求值并成为结果。
会发生这种情况。首先,检查左侧的x
,也就是a
,检查null
。但它本身并不是null
。那么左边的是要使用的。然后运行隐式转换。 B
类型的结果是 ... null
。
请注意,这不同于:
A a = new A();
B b = (B)a ?? new B();
在这种情况下,左侧操作数是一个表达式 (x
),它本身就是 null
,结果变成右侧 (y
)。
也许只有当原始类型是null
时,引用类型之间的隐式转换才应该返回null
(如果和),作为一种好习惯?
我想编写规范的人可以这样做(但没有):
• 否则,如果 y 具有 Y 类型并且存在从 x 到 Y 的隐式转换,则结果类型为 Y。在运行时,首先计算 x 并将其转换为 Y 类型。如果该转换的输出为不为空,该输出成为结果。否则,评估 y 并成为结果。
也许这样会更直观?无论转换的输入是否为null
,它都会强制运行时调用您的隐式转换。如果典型实现很快确定null → null
,那应该不会太昂贵。
【讨论】:
【参考方案4】:我们需要查看的部分是空合并表达式的编译时类型。
否则,如果 b 具有 B 类型并且存在从 a 到 B 的隐式转换,则结果类型为 B。在运行时,首先计算 a。如果 a 不为空,则将 a 解包为 A0 类型(如果 A 存在且可为空)并转换为 B 类型,这将成为结果。否则,b 被评估并成为结果。
将其放入伪代码:
public Tuple<Type, object> NullCoalesce<TA, TB>(TA a, TB b)
...
else if (a is TB) // pseudocode alert, this won't work in actual C#
Type type = typeof(TB);
object result;
if (a != null)
result = (TB)a; // in your example, this resolves to null
else
result = b;
return new Tuple<Type, object>(type, result);
...
【讨论】:
以上是关于使用空合并运算符进行隐式转换的主要内容,如果未能解决你的问题,请参考以下文章