我在前一篇随笔“浅谈 ConsoleColor”中把 ConsoleColor 枚举元素与同名的 KnownColor 枚举元素联系起来,发现 ConsoleColor.DarkYollew 在 KnownColor 找不到对应的元素。
Console 类中相关的源程序代码
实际上,ConsoleColor 枚举用于 Console 类的 ForegroundColor 和 BackgroundColor 属性,下面就是 Console 类中相关的源程序代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | namespace System { public static class Console { public static ConsoleColor BackgroundColor { [SecuritySafeCritical] get { bool flag; Win32Native.CONSOLE_SCREEN_BUFFER_INFO bufferInfo = GetBufferInfo( false , out flag); if (!flag) { return ConsoleColor.Black; } Win32Native.Color c = (Win32Native.Color) (( short ) (bufferInfo.wAttributes & 240)); return ColorAttributeToConsoleColor(c); } [SecuritySafeCritical] set { bool flag; new UIPermission(UIPermissionWindow.SafeTopLevelWindows).Demand(); Win32Native.Color color = ConsoleColorToColorAttribute(value, true ); Win32Native.CONSOLE_SCREEN_BUFFER_INFO bufferInfo = GetBufferInfo( false , out flag); if (flag) { short attributes = ( short ) (bufferInfo.wAttributes & -241); attributes = ( short ) ((( ushort ) attributes) | (( ushort ) color)); Win32Native.SetConsoleTextAttribute(ConsoleOutputHandle, attributes); } } } public static ConsoleColor ForegroundColor { [SecuritySafeCritical] get { bool flag; Win32Native.CONSOLE_SCREEN_BUFFER_INFO bufferInfo = GetBufferInfo( false , out flag); if (!flag) { return ConsoleColor.Gray; } Win32Native.Color c = (Win32Native.Color) (( short ) (bufferInfo.wAttributes & 15)); return ColorAttributeToConsoleColor(c); } [SecuritySafeCritical] set { bool flag; new UIPermission(UIPermissionWindow.SafeTopLevelWindows).Demand(); Win32Native.Color color = ConsoleColorToColorAttribute(value, false ); Win32Native.CONSOLE_SCREEN_BUFFER_INFO bufferInfo = GetBufferInfo( false , out flag); if (flag) { short attributes = ( short ) (bufferInfo.wAttributes & -16); attributes = ( short ) ((( ushort ) attributes) | (( ushort ) color)); Win32Native.SetConsoleTextAttribute(ConsoleOutputHandle, attributes); } } } [SecurityCritical] private static Win32Native.Color ConsoleColorToColorAttribute(ConsoleColor color, bool isBackground) { if ((color & ~ConsoleColor.White) != ConsoleColor.Black) { throw new ArgumentException(Environment.GetResourceString( "Arg_InvalidConsoleColor" )); } Win32Native.Color color2 = (Win32Native.Color) (( short ) color); if (isBackground) { color2 = (Win32Native.Color) (( short ) ((( short ) color2) << 4)); } return color2; } [SecurityCritical] private static ConsoleColor ColorAttributeToConsoleColor(Win32Native.Color c) { if ((( short ) (c & (Win32Native.Color.BackgroundYellow | Win32Native.Color.BackgroundIntensity | Win32Native.Color.BackgroundBlue))) != 0) { c = (Win32Native.Color) (( short ) ((( short ) c) >> 4)); } return (ConsoleColor) c; } } } |
从上述源程序代码可以看出,ConsoleColor 枚举最终是和 Microsoft.Win32.Win32Native 内部类中的 Color 枚举相联系的。通过 Console 类的以下两个私有静态方法互相转换:
- ConsoleColorToColorAttribute,第 65 行到第 78 行。
- ColorAttributeToConsoleColor,第 80 行到第 88 行。
对 ForegroundColor 属性来说,基本上是直接赋值,对 BackgroundColor 属性来说,就是左移或者右移 4 bits 后再赋值。
Win32Native.Color 枚举
这个 Win32Native.Color 枚举如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | namespace Microsoft.Win32 { [SuppressUnmanagedCodeSecurity, SecurityCritical] internal static class Win32Native { [Serializable, Flags] internal enum Color : short { BackgroundBlue = 0x10, BackgroundGreen = 0x20, BackgroundIntensity = 0x80, BackgroundMask = 240, BackgroundRed = 0x40, BackgroundYellow = 0x60, Black = 0, ColorMask = 0xff, ForegroundBlue = 1, ForegroundGreen = 2, ForegroundIntensity = 8, ForegroundMask = 15, ForegroundRed = 4, ForegroundYellow = 6 } } } |
注意上面的 Win32Native.Color 枚举被指定有 FlagsAttribute 特性,它将作为位域(一组标志)进行处理。它可以同时指定前景色(ForegroundColor)和背景色(BackgroundColor)。主要的标志有:
- ForegroundBlue = 1 和 BackgroundBue = 0x10
- ForegroundGreen = 2 和 BackgroundGreen = 0x20
- ForegroundRed = 4 和 BackgroundRed = 0x40
- ForegroundIntensity = 8 和 BackgroundIntensity = 0x80
前面三组对应 Blue、Green、Red 三原色,最后一个 Intensity 指示更亮的颜色。注意,ForegroundYellow = 6 只不过是 ForegroundGreen = 2 和 ForegroundRed = 4 的组合而已。ForegroundMask = 15 和 ColorMask = 0xff 也不是用来指定颜色的标志,只不过是一些方便使用的 mask 而已。
CHAR_INFO 结构的 Attributes 字段
实际上,这个 Win32Native.Color 最终对应到 Windows SDK 中 CHAR_INFO 结构中的 Attributes 字段,下面是 Attributes 字段的相关部分:
名称 | 值 | 描述 |
---|---|---|
FOREGROUND_BLUE | 0X0001 | Text color contains blue. |
FOREGROUND_GREEN | 0X0002 | Text color contains green. |
FOREGROUND_RED | 0X0004 | Text color contains red. |
FOREGROUND_INTENSITY | 0X0008 | Text color is intensified. |
BACKGROUND_BLUE | 0X0010 | Background color contains blue. |
BACKGROUND_GREEN | 0X0020 | Background color contains green. |
BACKGROUND_RED | 0X0040 | Background color contains red. |
BACKGROUND_INTENSITY | 0X0080 | Background color is intensified. |
可以看出,这里是没有 YELLOW 的。而 ConsoleColor 枚举的十六个元素的值是经过仔细挑选的,正好和这些标志位对应:
名称 | 值 | 二进制 | Intensity | Red | Green | Blue |
---|---|---|---|---|---|---|
Black | 0 | 0000 | 0 | 0 | 0 | 0 |
DarkBlue | 1 | 0001 | 0 | 0 | 0 | 1 |
DarkCreen | 2 | 0010 | 0 | 0 | 1 | 0 |
DarkCyan | 3 | 0011 | 0 | 0 | 1 | 1 |
DarkRed | 4 | 0100 | 0 | 1 | 0 | 0 |
DarkMagenta | 5 | 0101 | 0 | 1 | 0 | 1 |
DarkYellow | 6 | 0110 | 0 | 1 | 1 | 0 |
Gray | 7 | 0111 | 0 | 1 | 1 | 1 |
DarkGray | 8 | 1000 | 1 | 0 | 0 | 0 |
Blue | 9 | 1001 | 1 | 0 | 0 | 1 |
Green | 10 | 1010 | 1 | 0 | 1 | 0 |
Cyan | 11 | 1011 | 1 | 0 | 1 | 1 |
Red | 12 | 1100 | 1 | 1 | 0 | 0 |
Magenta | 13 | 1101 | 1 | 1 | 0 | 1 |
Yellow | 14 | 1110 | 1 | 1 | 1 | 0 |
White | 15 | 1111 | 1 | 1 | 1 | 1 |
上表中 ConsoleColor 枚举的这十六个元素的值正好用于设定 Win32Native.Color 枚举的相关标志位,最终对应到 Windows SDK 中 CHAR_INFO 结构中的 Attributes 字段的相关标志位,指示在 Windows 操作系统中应该显示的颜色。
KnownColor 枚举和 Color 结构
对应到 .NET Framework Base Class Library 中的 KnownColor 枚举和 Color 结构,就如下图所示:
这是通过修改上一篇随笔中的 ConsoleColorTester.cs 程序后得到的运行结果,修改后的 ConsoleColorTester.cs 程序如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | using System; using System.Data; using System.Drawing; using System.Windows.Forms; namespace Skyiv.Tester { sealed class ConsoleColorTester : Form { DataGridView dgv; ConsoleColorTester() { Text = "ConsoleColor - " + Environment.OSVersion; Size = new Size(600, 380); dgv = new DataGridView(); dgv.Dock = DockStyle.Fill; Controls.Add(dgv); } protected override void OnLoad(EventArgs e) { dgv.DataSource = GetConsoleColors(); dgv.RowHeadersVisible = false ; dgv.AllowUserToAddRows = false ; dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells; dgv.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells; dgv.ReadOnly = true ; foreach (DataGridViewColumn column in dgv.Columns) column.SortMode = DataGridViewColumnSortMode.NotSortable; foreach (DataGridViewRow row in dgv.Rows) row.Cells[10].Style.BackColor = (Color)row.Cells[2].Value; base .OnLoad(e); } DataTable GetConsoleColors() { var dt = GetDataTable(); foreach (ConsoleColor consoleColor in Enum.GetValues( typeof (ConsoleColor))) { var color = GetKnownColor(consoleColor); var dr = dt.NewRow(); dr[0] = consoleColor; dr[1] = consoleColor; dr[2] = color; dr[3] = color.A; dr[4] = color.R; dr[5] = color.G; dr[6] = color.B; dr[7] = color.GetHue(); dr[8] = color.GetSaturation(); dr[9] = color.GetBrightness(); dt.Rows.Add(dr); } return dt; } DataTable GetDataTable() { var dt = new DataTable(); dt.Columns.Add( "值" , typeof ( int )); dt.Columns.Add( "名称" , typeof ( string )); dt.Columns.Add( "Color" , typeof (Color)); dt.Columns.Add( "Alpha" , typeof ( byte )); dt.Columns.Add( "Red" , typeof ( byte )); dt.Columns.Add( "Green" , typeof ( byte )); dt.Columns.Add( "Blue" , typeof ( byte )); dt.Columns.Add( "色调" , typeof ( float )); dt.Columns.Add( "饱和度" , typeof ( float )); dt.Columns.Add( "亮度" , typeof ( float )); dt.Columns.Add( " 颜色 " , typeof ( string )); return dt; } Color GetKnownColor(ConsoleColor color) { if (color == ConsoleColor.DarkBlue) return Color.Navy; if (color == ConsoleColor.DarkGreen) return Color.Green; if (color == ConsoleColor.DarkCyan) return Color.Teal; if (color == ConsoleColor.DarkRed) return Color.Maroon; if (color == ConsoleColor.DarkMagenta) return Color.Purple; if (color == ConsoleColor.DarkYellow) return Color.Olive; if (color == ConsoleColor.Green) return Color.Lime; return Color.FromName(color.ToString()); } static void Main() { Application.Run( new ConsoleColorTester()); } } } |
在上述程序中,主要是增加了第 75 行到第 85 行的 GetKnownColor 方法,将 System.ConsoleColor 枚举的值按照前述方案(即根据 Red、Green、Blue 的值)转换为 System.Drawing.KnownColor 枚举相应的值。这十六个值中,有七个需要手工指定转换关系,其余九个枚举元素的名称一致,可以直接使用 System.Drawing.Color 类的 FromName 静态方法转换。第 82 行将 ConsoleColor.DarkYellow 转换为 Color.Olive,正好和我在上一篇随笔中根据颜色的相近程度所猜想的一致。
给 Microsoft 的建议
虽然 System.ConsleColor 枚举只用在 Console 类的 ForegroundColor 和 BackgroundColor 属性中,最终是和 Win32Native.Color 相联系的。但是,Microsoft 在设计 ConsoleColor 枚举时,要是让其元素的名称和 KnownColor 枚举中相应元素的名称一致的话,就显得更协调了。
[Serializable, Obsolete] public enum ConsoleColor { Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White } |
[Serializable] public enum Console2Color { Black, Navy, Green, Teal, Maroon, Purple, Olive, Gray, DarkGray, Blue, Lime, Cyan, Red, Magenta, Yellow, White } |
上面左边是现有的方案。当然,为了向后兼容,是不可能修改 ConsoleColor 了。但是,可以建议 Microsoft 给 ConsoleColor 枚举加上 ObsoleteAttribute 特性。而新增加一个 Console2Color 枚举,如上面右边所示。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?