Console.WriteLine(Enum.Value)在C#和VB.Net中的输出不同。

4

我基本上是一个C#开发者,但最近在写VB.Net代码。

今天我遇到了一个非常不同的.Net行为。

C#代码:

enum Color
{
   Red,
   Green,
   Blue
}

class Demo
{
    public static void Main()
    {
        System.Console.WriteLine(Color.Red);
    }
}

这段代码输出的是 Red

但当使用VB.Net编写该代码时,它会输出0

VB.Net 代码

Module Module1

    Sub Main()
        System.Console.WriteLine(Color.Red)
    End Sub

End Module

Enum Color
    Red
    Green
    Blue
End Enum

为什么如此不同?

WriteLine方法期望一个字符串时,您正在使用数字Color.Red。C#处理此问题的方式似乎是自动调用返回“Red”的ToString方法,而VB将数字0转换为字符串“0”。在这两种语言中,您可以通过使用Color.Red.ToString()来确保写入的内容是“Red”。 - Blackwood
Console.WriteLine(Color.Red.ToString()) 将打印枚举名称,否则它将打印值。至于“为什么”,这可能只有来自 MS 的人才能回答。 - Ňɏssa Pøngjǣrdenlarp
3
“WriteLine”不仅接收字符串,它有许多重载形式。我更愿意说这两种语言具有不同的重载解析规则。C#选择“WriteLine(object)”,而VB选择“WriteLine(int)”。 - Lucas Trzesniewski
1
看起来是编译器问题/特性。编译后的代码变成了 Console.WriteLine(0) - Bjørn-Roger Kringsjå
@LucasTrzesniewski 这似乎是一个更好的解释。 - Blackwood
2个回答

6

没有"Console.WriteLine(Enum)"重载,因此编译器被迫选择其他重载方法。重载决策规则非常晦涩,VB.NET和C#规则不同,但只要存在隐式转换到目标参数类型的情况,两个编译器都愿意选择一个并采取最小化工作量的方法。

这就是另一条规则适用的地方,在VB.NET中,这种语句是完全有效的:

   Dim example As Integer = Color.Red  '' Fine

但是C#编译器在这个地方报错:
   int example = Color.Red;            // CS0266

坚持使用(int)强制转换。它仅具有显式转换,而不像VB.NET一样具有隐式转换。
因此,C#编译器将忽略所有接受整数参数的重载,因为它们只有显式转换。除了一个,Console.WriteLine(Object)重载。这个重载存在一个隐式转换,它采用装箱转换。
VB.NET编译器也能看到这一点,但现在“更好”的转换出现了。装箱转换是一种非常昂贵的转换,而转换为Integer则非常便宜。它不需要额外的代码。所以它更喜欢后者。
解决方法很简单:
    System.Console.WriteLine(CObj(Color.Red))         '' or
    System.Console.WriteLine(Color.Red.ToString())

5

C#和VB.NET有不同的方法重载解析规则。

C#选择Console.WriteLine(Object),而VB.NET选择Console.WriteLine(Int32)。让我们看看为什么会这样。

VB.NET规则

  1. 可访问性。 它通过访问级别来防止调用代码过载。

  2. 参数数量。 它会消除定义与调用时不同数量参数的重载。

  3. 参数数据类型。 编译器比扩展方法更喜欢实例方法。如果找到任何实例方法,只需要进行宽化转换即可匹配过程调用,则删除所有扩展方法,并继续使用仅实例方法候选项。如果找不到这样的实例方法,则继续使用实例和扩展方法。
    在此步骤中,它会消除调用参数的数据类型无法转换为重载中定义的参数类型的任何重载。

  4. 缩小转换。 它消除要求从调用参数类型到定义的参数类型进行缩小转换的任何重载。无论类型检查开关(Option Strict语句)是打开还是关闭都是如此。

  5. 最小宽化。 编译器将剩余的重载成对考虑。对于每一对,它比较定义参数的数据类型。如果其中一个重载中的类型全部扩大到另一个重载中相应的类型,则编译器消除后者。 也就是说,它保留需要最少扩展的重载。

  6. 单个候选项。 它继续成对考虑重载,直到仅剩一个重载,并将调用解析为该重载。如果编译器无法将重载减少到单个候选项,则会生成错误。

有很多WriteLine的重载,其中一些在第3步被丢弃了。我们基本上只剩下以下可能性:Object和数字类型。

这里的第5点很有趣:最小扩展。那么扩展规则是什么呢?

任何枚举类型(Enum)都会扩展到其基础整数类型以及其基础类型扩展的任何类型。

任何类型都会扩展到Object

因此,您的Color枚举首先扩展为Int32(其基础数据类型)- 这与Console.WriteLine(Int32)完全匹配。它需要另一个扩展转换才能从Int32转换为Object,但是上述规则说要保留需要最少扩展量的重载。


关于 C#(来自于C# 5规范第7.5.3.2节):
给定一个参数列表A,其中包含一组参数表达式{E1, E2, ..., EN}和两个适用的函数成员MPMQ,其参数类型分别为{P1, P2, ..., PN}{Q1, Q2, ..., QN},如果对于每个参数,从EXQX的隐式转换不比从EXPX更好,并且对于至少一个参数,从EXPX的转换优于从EXQX的转换,则定义MPMQ更好。

好的,那么什么是更好的定义(§7.5.3.4)?

给定从类型 S 转换到类型 T1 的转换 C1 和从类型 S 转换到类型 T2 的转换 C2,如果以下至少一项成立,则 C1 是比 C2 更好的转换:

  • ST1 存在一个恒等转换,但不存在从 ST2 的恒等转换
  • T1 是比 T2 更好的转换目标(§7.5.3.5)

让我们看看 §7.5.3.5:

给定两种不同类型 T1T2,如果满足以下至少一条,则 T1 是比 T2 更好的转换目标:
  • T1T2 存在隐式转换,并且不存在从 T2T1 的隐式转换
  • T1 是有符号整数类型,而 T2 是无符号整数类型。
所以,我们要将 Color 转换为 ObjectInt32 中的一个。根据这些规则,哪个更好?
  • Color类型可以隐式转换为Object类型。
  • 没有从ObjectColor的隐式转换(显然)。
  • C#中没有从ColorInt32的隐式转换(这些是显式的)。
  • 除了0之外,没有从Int32Color的隐式转换。

规范§6.1:

以下转换被归类为隐式转换:

  • 恒等转换
  • 隐式数字转换
  • 隐式枚举转换
  • 隐式可空类型转换
  • 空字面量转换
  • 隐式引用转换
  • 装箱转换
  • 隐式动态转换
  • 隐式常量表达式转换
  • 用户定义的隐式转换
  • 匿名函数转换
  • 方法组转换
隐式数字转换没有提到枚举类型,而隐式枚举转换则相反:
隐式枚举转换允许将十进制整数文字“0”转换为任何枚举类型和底层类型为枚举类型的任何可空类型。在后一种情况下,通过将其转换为底层枚举类型并包装结果(§4.1.10)来评估转换。
枚举由装箱转换处理(§6.1.7):
装箱转换允许将值类型隐式转换为引用类型。从任何非可空值类型到object和dynamic,到System.ValueType以及由非可空值类型实现的任何接口类型都存在装箱转换。此外,枚举类型可以转换为类型System.Enum。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接