21

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

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 }

下面的第一个if编译;第二个没有:

 if ((x.Y?.Color).IsOneOf(Color.Red, Color.Green))
 ;
 if (x.Y?.Color.IsOneOf(Color.Red, Color.Green))
 ;

它们仅因额外的一组括号而异。为什么我必须这样做?

起初我怀疑它是从bool?tobool然后返回到的双重隐式转换bool?,但是当我删除第一个扩展方法时,它抱怨没有隐式转换 from boolto bool?。然后我检查了IL,没有演员表。反编译回 C# 会产生如下所示的内容:

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

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

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

更新

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

在此处输入图像描述

4

4 回答 4

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;

尽管

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;

然后有

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.

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

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

于 2016-05-24T21:22:18.670 回答
2

的含义x.Y?.Color.IsOneOf(Color.Red, Color.Green)完全独立于您的方法重载。?.的伪操作数是x.Yand Color.IsOneOf(Color.Red, Color.Green),即使后者本身不是一个有效的表达式。首先,x.Y被评估。调用结果tmp。如果tmp == null那么整个表达式是null。如果tmp != null,则整个表达式的计算结果为tmp.Color.IsOneOf(Color.Red, Color.Green)

IsOneOf是一个实例方法时,这几乎总是你想要的行为,这就是为什么最终进入规范和编译器的行为。

什么时候IsOneOf是扩展方法,就像在您的示例中那样,那么它可能不是您想要的行为,但行为是相同的,为了保持一致性,并且您已经找到了一个现成的解决方法。

Roslyn CodePlex 网站上的一个线程解释了关联性以及选择当前结果的原因。

于 2016-05-24T21:11:12.230 回答
1

好的,我想通了,但我会接受 Tamas 的回答。不过,我认为我的在这里有价值,可以作为使用空条件时的思考辅助。

在我的问题的屏幕截图中,答案有点像盯着我看

类型转换错误

我对Color/Color?转换进行了隧道视野,但实际问题是bool?不能隐式转换为bool.

在包含运算符的长“点”表达式中,您必须在最右边的点之后?.考虑事物及其类型。在这种情况下,那个东西是返回 a 的扩展方法,但是使用有效地将其“更改”为 a 。换句话说,把最右边的东西看作是一个引用类型,它可以是或.bool?.bool?nullNullable<T>

哇,这个让我去了。

于 2016-05-24T21:20:44.147 回答
0

如果您放置以下行,您将看到 .net 正在System.Nullable<UserQuery+Color>为括号内的这个空条件表达式创建一个动态类型。

Console.WriteLine((x.Y?.Color).GetType().ToString());

如果您不放置括号,它可能会尝试将其评估为正常表达式,从而导致错误。

于 2016-05-24T20:08:07.690 回答