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

Nik*_*wal 4 c# vb.net enums

我基本上是一个C#家伙,但这些天写的是VB.Net代码.

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

C#代码

enum Color
{
   Red,
   Green,
   Blue
}

class Demo
{
    public static void Main()
    {
        System.Console.WriteLine(Color.Red);
    }
}
Run Code Online (Sandbox Code Playgroud)

这打印 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
Run Code Online (Sandbox Code Playgroud)

为什么这么不同?

Han*_*ant 6

没有Console.WriteLine(Enum)重载,因此编译器被迫选择其中一个.重载决策规则非常晦涩,VB.NET和C#规则不一样,但是当存在对目标参数类型的隐式转换并且选择工作量最少的编译器时,两个编译器都愿意选择一个.

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

   Dim example As Integer = Color.Red  '' Fine
Run Code Online (Sandbox Code Playgroud)

但是C#编译器吐出:

   int example = Color.Red;            // CS0266
Run Code Online (Sandbox Code Playgroud)

坚持你应用(int)演员.它只有一个显式转换,而不是像VB.NET这样的隐式转换.

所以C#编译器将忽略所有带有整数参数的重载,没有一个是候选者,因为它们只存在显式转换.除了一个,Console.WriteLine(Object)重载.还有就是对于一个隐式转换,它需要一个装箱转换.

VB.NET编译器也看到它,但现在"更好"的转换发挥作用.拳击转换是一种非常昂贵的转换,转换Integer非常便宜.它不需要额外的代码.所以它更喜欢那个.

解决方法很简单:

    System.Console.WriteLine(CObj(Color.Red))         '' or
    System.Console.WriteLine(Color.Red.ToString())
Run Code Online (Sandbox Code Playgroud)


Luc*_*ski 5

C#和VB.NET有不同的方法重载决策规则.

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

VB.NET规则:

  1. 可访问性.它消除了访问级别的任何重载,阻止调用代码调用它.

  2. 参数数量.它消除了定义与调用中提供的参数数量不同的任何重载.

  3. 参数数据类型.编译器提供实例方法首选项而不是扩展方法.如果找到任何只需要加宽转换以匹配过程调用的实例方法,则会删除所有扩展方法,并且编译器仅继续使用实例方法候选.如果没有找到这样的实例方法,它将继续实例和扩展方法.
    在此步骤中,它消除了无法将调用参数的数据类型转换为重载中定义的参数类型的任何重载.

  4. 缩小转化次数.它消除了需要从调用参数类型到定义的参数类型的缩小转换的任何重载.无论类型检查开关(Option Strict Statement)是On还是Off,都是如此.

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

  6. 单候选人.它继续成对地考虑重载,直到只剩下一个重载,并且它解决了对该重载的调用.如果编译器无法减少单个候选项的重载,则会生成错误.

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

第五点在这里很有趣:最少扩展.那么扩大规则说什么呢?

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

任何类型都扩大到 Object

所以,你的Color枚举首先扩展到Int32(它的基础数据类型) - 这是100%的匹配Console.WriteLine(Int32).这将需要另外扩大转换从去Int32Object,但上述规则说要保留需要加宽量最少的过载.


至于C#(来自§7.5.3.2 的C#5规范):

给定一个参数列表A与一组参数表达式的{ E1, E2, ..., EN }和两个适用函数成员MPMQ与参数类型{ P1, P2, ..., PN }{ Q1, Q2, ..., QN },MP被定义为是一个更好的功能部件比MQ如果

  • 为每个参数,从隐式转换EXQX距离比隐式转换不是更好EXPX,并
  • 对于至少一个参数,从EXto PX转换比从EXto 转换更好QX.

好的,现在如何更好地定义(§7.5.3.4)?

给定一个转换C1,从一种类型转换S到一个类型T1,并转换C2一个从类型转换S到一个类型T2,C1比更好的转换C2如果下列中的至少一个成立:

  • 标识转换存在来自ST1而不是从ST2
  • T1是一个比T2(§7.5.3.5)更好的转换目标

让我们看看§7.5.3.5:

给出了两种不同类型T1T2,T1是更好的转换目标比T2如果以下中的至少一个成立:

  • 从隐式转换T1T2存在,从没有隐式转换T2T1存在
  • T1是有符号整数类型,T2是无符号整数类型.

所以,我们正在转变ColorObject或者Int32.根据这些规则,哪一个更好?

  • 这里一个隐式转换从ColorObject
  • 有没有隐式转换来自ObjectColor(显然)
  • 有没有隐式转换来自ColorInt32(这是在C#中明确)
  • 没有隐式转换从Int32Color(除0)

规范§6.1:

以下转化归类为隐式转化:

  • 身份转换
  • 隐式数字转换
  • 隐式枚举转换.
  • 隐式可空转换
  • 空字面转换
  • 隐式引用转换
  • 拳击转换
  • 隐式动态转换
  • 隐式常量表达式转换
  • 用户定义的隐式转换
  • 匿名函数转换
  • 方法组转换

隐式数字转换没有提及枚举类型,隐式枚举转换处理相反的方式:

隐式枚举转换允许将decimal-integer-literal 0转换为任何枚举类型和任何可以为其类型为枚举类型可空类型.在后一种情况下,通过转换为基础枚举类型并包装结果来评估转换(第4.1.10节).

枚举由装箱转换处理(第6.1.7节):

装箱转换允许将值类型隐式转换为引用类型.装箱转换从任何存在非可空值型objectdynamic,以System.ValueType及任何接口类型由所实现的非可空值型.此外,枚举类型可以转换为类型System.Enum.