英文:
C# Target-Typed Conditional Expression
问题
以下是翻译好的部分:
在引入此功能后,文档提到了以下情景中的“模糊性”,但似乎每次仍然调用M(long, long)
。也就是说,没有模糊性?
M(b ? 1 : 2, 1); // 在没有此功能的情况下调用M(long, long); 使用此功能时会引发模糊性。 static void M(short i, short ii) => Console.WriteLine("short"); static void M(long i, long ii) => Console.WriteLine("long");
编译为:
public static void Main()
{
M(1L, 1L);
}
private static void M(short i, short ii) { Console.WriteLine("short, short"); }
private static void M(long i, long ii) { Console.WriteLine("long, long");
英文:
Looking at the documentation for Target-Typed Conditional Expression in C# 9.
A reference is made to ambiguity in the documentation for the following scenario after the introduction of this feature, but it still appears to call M(long, long)
every time. i.e. no ambiguity?
> M(b ? 1 : 2, 1); // calls M(long, long) without this feature; ambiguous with this feature.
>
> static void M(short i, short ii) => Console.WriteLine("short");
> static void M(long i, long ii) => Console.WriteLine("long");
Compiles to:
public static void Main()
{
M(1L, 1L);
}
private static void M(short i, short ii) { Console.WriteLine("short, short"); }
private static void M(long i, long ii) { Console.WriteLine("long, long");
答案1
得分: 1
The crux here is the ternary operator. In this case, it will upcast to Int32 (int
):
Console.WriteLine((b ? 1 : 2).GetType().Name);
Given the rules of overloading, the only valid overload available of M
is M(long, long)
.
If you explicitly type the numbers, then the ternary will resolve to Int16 (short
):
const short one = 1;
const short two = 2;
Console.WriteLine((b ? one : two).GetType().Name);
In this case, the closest overload of M
is M(short, short)
.
Another way:
Console.WriteLine((b ? (short)1 : (short)2).GetType().Name);
英文:
The crux here is the ternary operator. In this case, it will upcast to Int32 (int
):
Console.WriteLine((b ? 1 : 2).GetType().Name);
Given the rules of overloading, the only valid overload available of M
is M(long, long)
If you explicitly type the numbers, then the ternary will resolve to Int16 (short
):
const short one = 1;
const short two = 2;
Console.WriteLine((b ? one : two).GetType().Name);
In this case, the closest overload of M
is M(short, short)
Another way:
Console.WriteLine((b ? (short)1 : (short)2).GetType().Name);
答案2
得分: 1
描述的案例中的歧义完全是内部的。它不会暴露给用户。
为了清晰起见,这就是案例:
static void Main()
{
M(1, 2); // 案例 A
M(true ? 1 : 2, 3); // 案例 B
bool b = true;
M(b ? 1 : 2, 3); // 案例 C
}
static void M(short a, short b) => Console.WriteLine($"{a} {b} {a.GetType()}");
static void M(long a, long b) => Console.WriteLine($"{a} {b} {a.GetType()}");
案例 A 和 B 可以由生成器评估(案例 B 生成 IL 代码 M(1, 3);
- 条件甚至未呈现给运行时),因此两者都解析为 short
实现,但案例 C 是歧义所在。
当运行时需要决定调用哪个版本时,它首先进行一次遍历,并确定两个表达式解析为不同的类型。 b ? 1 : 2
在独立考虑时解析为 int
,而 3
也解析为 int
,但在这个问题的上下文中,模式匹配器需要将两者解析为 short
或 long
。
模式匹配器不想完全评估 b ? 1 : 2
(甚至确定可能的类型 - 请记住,这可能是一种昂贵且嵌套的情况),但它已经预定为类型 int
。3
可以表示为 short
以匹配第一个案例,但 int
不能隐式转换为 short
,只能转换为 long
,因此它迈出了第一步。
因此,我们有一个调用 M
类型为 M(long, short)
,这是模糊不清的。这可以通过以下方式解决:
- 将第二个参数
3
隐式转换为long
- 评估第一个参数
b ? 1 : 2
看看它是否可以表示为 short
在提供了这些选项后,如果存在第一个选项,运行时将始终选择第一个选项。从你发表的提案中,这被"澄清"为:
更新后的术语 参考(新提案在这里),在这种情况下,C1
是上述列表中的选项 1(不是条件表达式转换 - 即隐式),C2
是选项 2(是条件表达式转换),因此 C1
被认为是“更好的”。
链接中的最后一行对我澄清事情有所帮助:
因此,我们将条件表达式转换视为在转换中的最后一招,以保留现有行为。
基本上,它猜测最佳匹配是什么,在 b ? 1 : 2
中,两个结果都解析为 int
,所以在决定调用哪个方法时,它会做的最后一件事就是评估该条件,以确定 short
更合适,而 int
和 short
都可以隐式转换为 long
。
当然,这可以通过 M((short)(b ? 1 : 2), 3);
强制执行,但这样做有什么乐趣呢?
希望对你有帮助。
英文:
The ambiguity in the case you describe it entirely internal. It is not exposed to the user.
For clarity, this is the case:
static void Main()
{
M(1, 2); // Case A
M(true ? 1 : 2, 3); // Case B
bool b = true;
M(b ? 1 : 2, 3); // Case C
}
static void M(short a, short b) => Console.WriteLine($"{a} {b} {a.GetType()}");
static void M(long a, long b) => Console.WriteLine($"{a} {b} {a.GetType()}");
Case A and B can be evaluated by the builder (Case B generates the IL code M(1, 3);
- the conditional is not even presented to the runtime), so both resolve to the short
implementation, but case C is where the ambiguity lies.
When the runtime needs to decide which version to call, it does a first pass and determines that both expressions resolve to different types. b ? 1 : 2
resolves to an int
when considered on its own, and 3
also resolves to an int
, but in the context of this problem, the pattern matcher needs to either resolve both to short
or long
.
The pattern matcher doesn't want to fully evaluate b ? 1 : 2
, (or even determine the possible types - remember that this could be an expensive and nested case) but it has been predetermined to be of type int
. 3
can be expressed as short
to match the first case, but int
can't be implicitly cast to short
, only to long
, so it makes that first step.
Therefore we have a call to M
of type M(long, short)
, which is ambiguous. This can be solved by either:
- Implicitly casting the second argument
3
to along
- Evaluating the first parameter
b ? 1 : 2
to see if it can be expressed as a short
When presented with these options, the runtime will always do the first one if that option exists. From the proposal you posted, this is 'clarified' by:
The updated terminology referenced (the new proposal is here), C1
in this case is option 1 in that list above (not a conditional expression conversion - i.e. implicit), and C2
is option 2 (IS a conditional expression conversion), so C1
is considered 'Better'.
The last line in the link is what clarified things for me:
> Therefore we treat the conditional expression conversion as a last resort in a cast, to preserve existing behavior.
Basically, it guesses what the best fit will be, and in b ? 1 : 2
, both outcomes resolve to int
, so when it comes to deciding which method to call, the last thing it will do is evaluate that conditional to figure out that a short
works better, and both int
and short
are implicitly convertible to long
Of course, this can be forced with M((short)(b ? 1 : 2), 3);
, but where's the fun in that?
Hope this helps.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论