为什么我必须在 null 条件表达式周围放置 () 才能使用正确的方法重载?
Why do I have to place () around null-conditional expression to use the correct method overload?
我有这些扩展方法和枚举类型:
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?
到 bool
然后又返回到 bool?
的双重隐式转换,但是当我删除第一个扩展方法时,它在那里抱怨没有从 bool
到 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 行不会有同样的问题。
如果放置以下行,您会看到 .net 正在为括号内的此空条件表达式创建动态类型 System.Nullable<UserQuery+Color>
。
Console.WriteLine((x.Y?.Color).GetType().ToString());
如果您不放置括号,它可能会尝试将其作为普通表达式求值,因此会出现错误。
x.Y?.Color.IsOneOf(Color.Red, Color.Green)
的含义完全独立于您的方法重载。 ?.
的伪操作数是 x.Y
和 Color.IsOneOf(Color.Red, Color.Green)
,即使后者本身不是有效表达式。首先,评估 x.Y
。调用结果 tmp
。如果 tmp == null
那么整个表达式就是 null
。如果 tmp != null
,则整个表达式的计算结果为 tmp.Color.IsOneOf(Color.Red, Color.Green)
。
当 IsOneOf
是一个实例方法时,这几乎总是您想要的行为,这就是为什么这种行为最终进入规范和编译器的原因。
当 IsOneOf
是一个扩展方法时,就像在您的示例中一样,那么它可能不是您想要的行为,但行为是相同的,为了保持一致性并且因为有现成的您已经找到的解决方法。
好的,我明白了,但我要和 Tamas 一起去 。不过,我认为我的方法在这里很有价值,可以作为使用 null 条件时的思考辅助工具。
在我的问题的屏幕截图中,答案有点盯着我看
我对 Color
/Color?
转换的看法很狭隘,但实际问题是 bool?
不能隐式转换为 bool
。
在包含 ?.
运算符的长 "dotted" 表达式中,您必须考虑事物及其类型,在最右边的点之后.那个东西,在这种情况下,是returns一个bool
的扩展方法,但是使用?.
有效地"changes"它到一个bool?
。换句话说,将最右边的东西视为可以是 null
或 Nullable<T>
.
的引用类型
哇,这个让我兴奋不已。
首先,这种行为在我看来是故意的。很少有人向可空类型添加扩展方法,人们在一个表达式中混合空条件访问和常规成员访问是很常见的,因此语言倾向于后者。
考虑以下示例:
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.c
和 a?.b?.c
。如果 a
为空,我们期望在 none 个案例中获得 NPE。为什么?因为在 a
之后直接有一个 null 条件。因此 .c
部分必须仅在 a
不为空时才进行评估,从而使成员访问依赖于先前的空条件结果。通过添加显式括号,(a?.b).c
我们强制编译器尝试访问 (a?.b)
的 .c
而不管 a
是否为 null,从而阻止它 "short circuit" 整个表达式为空。 (使用@JamesBuck -s 的话)
在你的情况下,x.Y?.Color.IsOneOf(Color.Red, Color.Green)
就像 a?.b.c
。它将仅在 x.Y
不为 null 时调用具有签名 bool IsOneOf(Color red)
的函数(因此参数不可为 null 的重载,并且我剥离了通用部分),从而将表达式的类型包装在 Nullable 中处理 x.Y
为空的情况。并且因为 while 的计算结果为 bool?
而不是 bool
,它不能用作 if 语句中的测试。
我有这些扩展方法和枚举类型:
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?
到 bool
然后又返回到 bool?
的双重隐式转换,但是当我删除第一个扩展方法时,它在那里抱怨没有从 bool
到 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 行不会有同样的问题。
如果放置以下行,您会看到 .net 正在为括号内的此空条件表达式创建动态类型 System.Nullable<UserQuery+Color>
。
Console.WriteLine((x.Y?.Color).GetType().ToString());
如果您不放置括号,它可能会尝试将其作为普通表达式求值,因此会出现错误。
x.Y?.Color.IsOneOf(Color.Red, Color.Green)
的含义完全独立于您的方法重载。 ?.
的伪操作数是 x.Y
和 Color.IsOneOf(Color.Red, Color.Green)
,即使后者本身不是有效表达式。首先,评估 x.Y
。调用结果 tmp
。如果 tmp == null
那么整个表达式就是 null
。如果 tmp != null
,则整个表达式的计算结果为 tmp.Color.IsOneOf(Color.Red, Color.Green)
。
当 IsOneOf
是一个实例方法时,这几乎总是您想要的行为,这就是为什么这种行为最终进入规范和编译器的原因。
当 IsOneOf
是一个扩展方法时,就像在您的示例中一样,那么它可能不是您想要的行为,但行为是相同的,为了保持一致性并且因为有现成的您已经找到的解决方法。
好的,我明白了,但我要和 Tamas 一起去
在我的问题的屏幕截图中,答案有点盯着我看
我对 Color
/Color?
转换的看法很狭隘,但实际问题是 bool?
不能隐式转换为 bool
。
在包含 ?.
运算符的长 "dotted" 表达式中,您必须考虑事物及其类型,在最右边的点之后.那个东西,在这种情况下,是returns一个bool
的扩展方法,但是使用?.
有效地"changes"它到一个bool?
。换句话说,将最右边的东西视为可以是 null
或 Nullable<T>
.
哇,这个让我兴奋不已。
首先,这种行为在我看来是故意的。很少有人向可空类型添加扩展方法,人们在一个表达式中混合空条件访问和常规成员访问是很常见的,因此语言倾向于后者。
考虑以下示例:
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.c
和 a?.b?.c
。如果 a
为空,我们期望在 none 个案例中获得 NPE。为什么?因为在 a
之后直接有一个 null 条件。因此 .c
部分必须仅在 a
不为空时才进行评估,从而使成员访问依赖于先前的空条件结果。通过添加显式括号,(a?.b).c
我们强制编译器尝试访问 (a?.b)
的 .c
而不管 a
是否为 null,从而阻止它 "short circuit" 整个表达式为空。 (使用@JamesBuck -s 的话)
在你的情况下,x.Y?.Color.IsOneOf(Color.Red, Color.Green)
就像 a?.b.c
。它将仅在 x.Y
不为 null 时调用具有签名 bool IsOneOf(Color red)
的函数(因此参数不可为 null 的重载,并且我剥离了通用部分),从而将表达式的类型包装在 Nullable 中处理 x.Y
为空的情况。并且因为 while 的计算结果为 bool?
而不是 bool
,它不能用作 if 语句中的测试。