为什么我必须在null条件表达式周围放置()才能使用正确的方法重载?

Kit*_*Kit 21 c# c#-6.0 null-conditional-operator

我有这些扩展方法和枚举类型:

public static bool IsOneOf<T>(this T thing, params T[] things)
{
    return things.Contains(thing);
}

public static bool IsOneOf<T>(this T? thing, params T[] things) where T : struct
{
    return thing.HasValue && things.Contains(thing.Value);
}

public enum Color { Red, Green, Blue }
Run Code Online (Sandbox Code Playgroud)

if下面的第一个编译; 第二个不是:

 if ((x.Y?.Color).IsOneOf(Color.Red, Color.Green))
 ;
 if (x.Y?.Color.IsOneOf(Color.Red, Color.Green))
 ;
Run Code Online (Sandbox Code Playgroud)

它们仅因额外的括号而异.为什么我要这样做?

起初我怀疑它是从做一个双隐式转换bool?bool,然后回bool?,但是当我删除第一个扩展方法,它抱怨不存在隐式转换,从boolbool?.然后我检查了IL并且没有演员阵容.反编译回C#会产生如下情况:

if (!(y != null ? new Color?(y.Color) : new Color?()).IsOneOf<Color>(new Color[2]
{
    Color.Red,
    Color.Green
}));
Run Code Online (Sandbox Code Playgroud)

这对于我正在运行的CLR版本以及我的期望是好的.我没想到的是x.Y?.Color.IsOneOf(Color.Red, Color.Green)不编译.

到底是怎么回事?它只是语言实现的方式,它需要()吗?

更新

这是一个显示上下文错误的屏幕截图.这让我更加困惑.错误实际上是有道理的; 但是什么(我现在不在意)是为什么第18行不会有同样的问题.

在此输入图像描述

Tam*_*dus 9

首先,这种行为对我来说是有意的.很少有人为可空类型添加扩展方法,人们在一个表达式中混合使用空条件和常规成员访问是很常见的,因此语言有利于后者.

请考虑以下示例:

class B { bool c; }
class A { B b; }
...

A a;
var output = a?.b.c; // infers bool?, throws NPE if (a != null && a.b == null)
// roughly translates to
// var output = (a == null) ? null : a.b.c;
Run Code Online (Sandbox Code Playgroud)

A a;
var output = (a?.b).c; // infers bool, throws NPE if (a == null || a.b == null)
// roughly translates to
// var output = ((a == null) ? null : a.b).c;
Run Code Online (Sandbox Code Playgroud)

然后有

A a;
var output = a?.b?.c; // infers bool?, *cannot* throw NPE
// roughly translates to
// var output = (a == null) ? null : (a.b == null) ? null : a.b.c;

// and this is almost the same as
// var output = (a?.b)?.c; // infers bool?, cannot throw NPE
// Only that the second `?.` is forced to evaluate every time.
Run Code Online (Sandbox Code Playgroud)

这里的设计目标似乎是帮助区分a?.b.ca?.b?.c.如果a为null,我们希望在任何情况下都不会获得NPE .为什么?因为之后有一个空条件a.因此,.c只有a在不为null的情况下才能评估该部分,使得成员访问依赖于先前的null-conditionals结果.通过添加显式括号,(a?.b).c我们强制编译器尝试访问.c(a?.b)不管是否为anull,从而阻止它将整个表达式"短路"为null.(使用@JamesBuck -s单词)

在你的情况下,x.Y?.Color.IsOneOf(Color.Red, Color.Green)就像a?.b.c.它将使用签名调用函数bool IsOneOf(Color red)(因此param不可为空的重载,并且我剥离了泛型部分)仅当x.Y非空时,因此将表达式的类型包装在Nullable中以处理x.Ynull 时的情况.并且因为while计算bool?而不是bool,它不能在if语句中用作测试.