首页 > 其他分享 >第6章 框架基础

第6章 框架基础

时间:2024-12-29 23:30:49浏览次数:5  
标签:Console string 框架 C7.0 基础 DateTime WriteLine 字符串

第6章 框架基础

6.1 字符串与文本处理

6.1.1 字符

C#中 char​ ​代表一个 Unicode 字符。char​ ​是 System.Char​ ​的别名,System.Char​ 定义了一系列静态方法对字符进行处理:

C7.0 核心技术指南 第7版.pdf - p267 - C7.0 核心技术指南 第 7 版-P267-20240205145109

C7.0 核心技术指南 第7版.pdf - p268 - C7.0 核心技术指南 第 7 版-P268-20240205145204

6.1.2 字符串

6.1.2.4 字符串内搜索

可以使用 StartsWith ​​、 EndsWith ​​、 Contains ​​、 IndexOf ​​、 LastIndexOf ​​、 IndexOfAny ​​、 LastIndexOfAny ​​ 进行搜索。搜索时可以使用 StringComparison ​​ 限定文化相关规则。

Console.WriteLine ("quick brown fox".Contains ("brown"));    // True
Console.WriteLine ("quick brown fox".EndsWith ("fox"));      // True

Console.WriteLine ("abcde".IndexOf ("cd"));   // 2
Console.WriteLine ("abcde".IndexOf ("xx"));   // -1

Console.WriteLine ("abcde".IndexOf ("CD", StringComparison.CurrentCultureIgnoreCase));    // 2

Console.WriteLine ("ab,cd ef".IndexOfAny (new char[] {' ', ','} ));       // 2
Console.WriteLine ("pas5w0rd".IndexOfAny ("0123456789".ToCharArray() ));  // 3

6.1.2.5 字符串处理

PadLeft​ ​和 PadRight​ ​会用特定的字符(如果未指定则使用空格)将字符串 填充为指定的长度

Console.WriteLine ("12345".PadLeft (9, '*'));  // ****12345
Console.WriteLine ("12345".PadLeft (9));       //     12345

如果输入字符串长度大于填充长度,则返回 不发生变化的原始 字符串。

TrimStart​ ​和 TrimEnd​ ​会从字符串的开始或结尾删除 指定的 字符,Trim​ ​则是从 开始结尾 执行删除操作。这些方法默认会删除 空白 字符(包括空格、制表符、换行和这些字符的 Unicode 变体):

Console.WriteLine ("  abc \t\r\n ".Trim().Length);   // 3

ToUpper​ 和 ToLower​ 默认情况下会受用户当前语言设置的影响; ToUpperInvariant ​ 和 ToLowerInvariant ​ 则总是应用英语字母表规则。

6.1.2.6 字符串的分割(Split)与连接(Join)

默认情况下,Split​ 使用 空白 字符作为分隔符。Split​ 还可以接受一个 StringSplitOptions​ 枚举值用以** 删除 **​ 空项 。这在一行文本中有多种单词分隔符时很有用。

Notice

默认参数下,面对连续的空白字符,Split​ 分隔时只会移除第一个空白,剩余空白仍会保留。此时可以传入上述 StringSplitOptions ​ 枚举参数。

静态方法 Join ​则执行和 Split ​相反的操作。它需要一个分隔符和字符串的数组:

string[] words = "The quick brown fox".Split();
string together = string.Join (" ", words);

6.1.2.7 String.Format​ 与组合格式字符串

调用 String.Format​ 时,需要提供一个组合格式字符串,后面紧跟每一个嵌入的变量。

花括号中的格式项(format item)对应参数的位置,后面可以跟随:

  1. 逗号与 最小宽度(minimum width)
  2. 冒号与 格式字符串(format string)

最小宽度用于对齐各个列。如果其值为负数,则为 对齐,否则为 对齐,例如:

composite = "Name={0,-20} Credit Limit={1,15:C}";

Console.WriteLine (string.Format (composite, "Mary", 500));
Console.WriteLine (string.Format (composite, "Elizabeth", 20000));

输出:

Name=Mary                 Credit Limit=        ¥500.00
Name=Elizabeth            Credit Limit=     ¥20,000.00

等价于:

s = "Name=" + "Mary".PadRight (20) + " Credit Limit=" + 500.ToString ("C").PadLeft (15);

Tips

当我们不需要最小宽度,或不需要格式字符串,都是可以省略的。如下代码分别省略了格式字符串和最小宽度:

var composite = "Name={0,-20} Credit Limit={1:C}";

Console.WriteLine(string.Format(composite, "Mary", 500));
Console.WriteLine(string.Format(composite, "Elizabeth", 20000));

6.1.3 字符串的比较

6.1.3.1 序列比较与文化相关的字符串比较

  • 序列比较(ordinal)

    按照 Unicode 字符数值 进行比较

  • 文化相关比较(culture-sensitive)

    • 不变文化(invariant culture)

      依托于美国文化,所有计算机都相同

    • 当前文化

      基于计算机控制面板的设定

假设有如下字符串:

Atom、atom、Zamia

使用不变文化其排序结果是:

atom、Atom、Zamia

使用序列比较排序结果是:

Atom、Zamia、atom

6.1.3.2 字符串的相等比较

字符串的 ==​​​ ​运算符执行的是 区分 大小写的** 序列 比较**,与 不带 参数的 String.Equals​​​ ​相同。

string​ 的 Equals​ 方法有静态版本, 推荐静态版本,因为它在字符串为 null 时仍然有效:

public bool Equals(string value, StringComparison comparisonType);
public static bool Equals(string a, string b, StringComparison comparisonType);

StringComparison ​是枚举类型,其定义如下:

public enum stringComparison
{
    CurrentCulture,
    CurrentCultureIgnoreCase,
    InvariantCulture,
    InvariantCultureIgnoreCase,
    Ordinal,
    OrdinalIgnoreCase
}

6.1.3.3 字符串的顺序比较

字符串顺序比较(CompareTo​​ 方法)执行** 文化 相关**的比较。其他类型的比较需调用静态方法 Compare ​ ​和 CompareOrdinal ​​ :

public static int Compare(string strA, string strB, StringComparison comparisonType);
public static int Compare(string strA, string strB, bool ignoreCase, CultureInfo culture);
public static int Compare(string strA, string strB, bool ignoreCase)
public static int CompareOrdinal(string strA, string strB)

后两个方法是前面两个方法的快捷调用形式。

6.1.4 StringBuilder

StringBuilder.AppendFormat​ 与 String.Format​ 相似,接受一个组合格式字符串。

此外,StringBuilder ​还有可写的** 索引器 **,用于获得/设置每一个字符。

StringBuilder.Length ​属性可写,可以将其设置为 0 以清除内容。

C7.0 核心技术指南 第7版.pdf - p276 - C7.0 核心技术指南 第 7 版-P276-20240205182444

6.1.5 文本编码和 Unicode

6.1.5.1 获取一个 Encoding 对象

我们可以通过 Encoding.GetEncoding ​ 方法获取编码:

Encoding utf8 = Encoding.GetEncoding ("utf-8");
Encoding chinese = Encoding.GetEncoding ("GB18030");

​也可以通过 Encoding ​的 静态属性 获得相应编码:

C7.0 核心技术指南 第7版.pdf - p278 - C7.0 核心技术指南 第 7 版-P278-20240205184839

还可以通过 实例化 获得相应编码。构造器有各种选项:

  1. 解码时,如果遇到无效字节,是否抛出异常。(默认为 false)
  2. 对 UTF-16/UTF-32 编码时,是大端存储还是小端存储。(Windows 默认为小端)
  3. 是否使用字节顺序标记。(BOM)

6.1.5.2 文件与流 I/O 编码

C7.0 核心技术指南 第7版.pdf - p278 - C7.0 核心技术指南 第 7 版-P278-20240205185935

6.1.5.4 UTF-16 和替代组

对于超过 16 位的字符,UTF-16 需要 char ​来表示,这会有两个问题:

  • 字符串的 Length ​ 属性值可能大于它的实际字符数。
  • 一个 char ​有时无法完整表示一个 Unicode 字符。

如果需要支持双字字符,那么可以用 char​ 类型下的静态方法将 32 位代码点转换为一个包含两个字符的字符串,同样也可以进行反向转换:

string ConvertFromUtf32 (int utf32);
int ConvertToUtf32 (char hightSurrogate, char lowSurrogate);

双字节字符每个字的范围在 0xD800~0xDFFF ,我们可以通过 char​ 的静态方法进行判断:

bool IsSurrogate     (char c);
bool IsHighSurrogate (char c);
bool IsLowSurrogate  (char c);
bool IsSurrogatePair (char hightSurrogate, char lowSurrogate);

6.2 日期和时间

6.2.1 TimeSpan

TimeSpan ​最小单位为 100 纳秒 ,最大值为 一千万天 ,可以为正数也可以为负数。

创建 TimeSpan 的方法有三种:

  1. 使用构造器
Console.WriteLine (new TimeSpan (2, 30, 0));     // 02:30:00
  1. 调用工厂方法 TimeSpan.FromXXX
Console.WriteLine (TimeSpan.FromHours (2.5));    // 02:30:00
Console.WriteLine (TimeSpan.FromHours (-2.5));   // -02:30:00
  1. 两个 DateTime 相减
Console.WriteLine (DateTime.MaxValue - DateTime.MinValue);

TimeSpan ​重载了 <​、>​、+ ​和 - ​运算符:

(TimeSpan.FromHours(2) + TimeSpan.FromMinutes(30)).Dump ("2.5 hours");

TimeSpan ​的默认值是 TimeSpan.Zero

6.2.2 DateTime​ 和 DateTimeOffset

最小单位均为 100 纳秒 ,取值范围为 ** 0001 到 ** 9999

6.2.2.1 使用 DateTime​ 还是 DateTimeOffset

DateTime​ 和 DateTimeOffset​ 在比较时有如下区别:

  1. DateTime​ 比较时会忽略 Kind状态标记 。当两个值的 年、月、日、时、分等 相同就认为它们是相等的。
  2. 如果两个值指的是 相同的时间点 ,那么 DateTimeOffset ​则认为它们是相等的。

因此,DateTime​ 认为如下时间是 不相等的DateTimeOffset​ 则认为是 相同的
July 01 2017 09:00:00 +00:00 (GMT)
July 01 2017 03:00:00 -06:00 (local time, Central America)

DateTimeOffset​ 适用于等值比较,但用户体验较差(需要在格式化前显式转换为本地时间);DateTime​ 适用于本地计算机的时间。

6.2.2.2 创建一个 DateTime

DateTime(..., DateTimeKind kind)

DateTime​ 构造器允许指定一个 DateTimeKind ​枚举,其元素如下:

  • Unspecified

    未指定时区,默认为该值。

  • Local

  • Utc

DateTime(..., Calendar calendar)

DateTime​​ 默认使用 历,其构造器可以接收 Calendar ​ ​对象,可以使用 System.Globalization​ ​下的 Calendar ​ ​子类来指定一个日期:

// 希伯来历
DateTime d = new DateTime (5767, 1, 1, new System.Globalization.HebrewCalendar());

Console.WriteLine (d);    // 12/12/2006 12:00:00 AM
DateTime(long tick)

还可以使用 long 类型的计数值(tick)来构造 DateTime​,其中计数值从 01/01/0001 年午夜开始计时,单位为 100ns

DateTime.FromFileTime(long fileTime) ​​ ​和 DateTime.FromFileTimeUtc(long fileTime) ​​​

上述静态方法将 Windows 文件时间转换为本地时间UTC 时间

DateTime.FromOADate(double d) ​​

将 OLE 时间转换为等效的 DateTime​ 时间。

OLE 时间通常以双精度浮点数(double)表示,其中整数部分代表自1899年12月30日以来的天数,小数部分代表一天中的具体时间。例如:

1.0 表示1899年12月31日(即基点日期后的第一天)。

2.5 表示1900年1月1日中午12点(即基点日期后的第二天,加上半天)。

6.2.2.3 创建一个 DateTimeOffset

DateTimeOffset​ ​和 DateTime​ ​具有类似的构造器,其区别是 DateTimeOffset​ ​还需要指定一个 TimeSpan ​ ​类型的 UTC 偏移量:

public DateTimeoffset (int year, int month, int day,
                       int hour, int minute, int second,
                       TimeSpan offset);
public DateTimeoffset (int year, int month, int day,
                       int hour, int minute, int second, int millisecond,
                       TimeSpan offset);

TimeSpan ​必须是 整数分钟数 ,否则构造器会抛出一个异常。

DateTime​ ​可以 式转换为 DateTimeOffset​​:

DateTimeOffset dt = new DateTime(2000, 2, 3);

6.2.2.4 获取当前 DateTime​/DateTimeOffset

DateTime​ ​有一个 Today ​ ​属性,返回日期的部分:

Console.WriteLine (DateTime.Today);    // 2024/2/6 0:00:00

这些方法(Now ​等)的返回精度取决于操作系统,一般情况下都在 10~20 毫秒范围内。

6.2.2.5 日期和时间的处理

DateTime​ 和 DateTimeOffset​ 都提供了如下相似的属性和方法(有删减):

  1. DateTime.DayOfWeek

  2. DateTime.DayOfYear

  3. DateTime.TimeOfDay

    返回 TimeSpan ​类型。

  4. DateTimeOffset.Offset

    输出 时区偏移量

另外,DateTime ​和 DateTimeOffset ​实例均可进行加减操作,其背后调用的是 Add() ​方法。

6.2.2.6 格式化和解析

DateTime ​有多种格式化方式:

  1. ToString()​​

    2024/2/6 17:57:28

  2. ToShortDateString()​​

    2024/2/6

  3. ToLongDateString()​​

    2024 年 2 月 6 日

  4. ToShortTimeString()​​

    17:57

  5. ToLongTimeString()​​

    17:57:28

DateTimeOffset​ 仅有 ToString() ​方法。

C7.0 核心技术指南 第7版.pdf - p287 - C7.0 核心技术指南 第 7 版-P287-20240206180202

使用“o”,则输出的是如下格式:

2024-02-06T18:01:33.1248308+08:00

Parse​/TryParse​ 和 ParseExact​/TryParseExact​ 静态方法则执行和 ToString​ 相反的操作:它们将字符串转换为 DateTime(Offset) ​ 。这些方法同样进行了重载以接受格式提供器。

6.2.2.7 DateTime ​和 DateTimeOffset ​空值

由于 DateTime​ 和 DateTimeOffset​ 都是结构体因此它们是不能为 null。当需要将其设置为 null 的时候可以使用如下两种方法:

  1. 使用 Nullable ​类型(例如 DateTime? ​或 DateTimeOffset?​)。
  2. 使用 DateTime(Offset).MinValue ​ 静态字段(它们同时也是这些类型的默认值)。

使用可空类型通常是最佳方法,因为编译器可以防止代码出现错误。DateTime.MinValue ​对于兼容 C#2.0(引入了可空类型)之前编写的代码是很有用的。

C7.0 核心技术指南 第7版.pdf - p287 - C7.0 核心技术指南 第 7 版-P287-20240206180806

6.3 日期和时区

6.3.1 DateTime​ 与时区

DateTime​ 有如下实例方法用于转换得到其他类型 DateTimeKind​ 的时间:

  • ToUniversalTime() ​​
  • ToLocalTime() ​​

还可以通过 DateTime.SpecifyKind ​静态方法创建一个 Kind ​不同的 DateTime​:

DateTime d = new DateTime (2000, 12, 12);  // Unspecified
DateTime utc2 = DateTime.SpecifyKind (d, DateTimeKind.Utc);

注意:上述方法仅改变 Kind ​属性,不改变时间内容。

6.3.2 DateTimeOffset​ 与时区

DateTimeOffset​ ​内部包括一个总是 UTC 的 DateTime ​ ​字段和一个用 16 位整数表示的以 分钟 计量的 UTC 偏移量。在比较时仅仅比较(UTC 的) DateTime 字段 ,而偏移量主要用于 格式化

DateTimeOffset​ 有如下实例方法用于转换得到其他 UTC 的 DateTimeOffset​ 时间:

  • ToUniversalTime()
  • ToLocalTime()

DateTime ​不同,这些方法不会影响底层的日期/时间值,只是影响 偏移量 ,时间点相同,其比较结果 也相同

如果要在比较中将 Offset​(偏移量)也考虑在内,可以使用 EqualsExact ​ 方法,如下代码将输出“ True,False ”:

DateTimeOffset local = DateTimeOffset.Now;
DateTimeOffset utc   = local.ToUniversalTime();

Console.WriteLine (local == utc);
Console.WriteLine (local.EqualsExact(utc));

6.3.3 TimeZone​ 和 TimeZoneInfo

TimeZone ​和 TimeZoneInfo ​类均提供时区名称、时区的 UTC 偏移量和夏令时规则。

  • TimeZone​ 可以访问 本地 时区。
  • TimeZoneInfo​ 可以访问 全世界 时区,具有更加丰富的(虽然有时使用不便)夏令时描述规则模型。

6.3.3.1 TimeZone

TimeZone.CurrentTimeZone ​ 静态方法会根据本地设置返回一个 TimeZone​ 实例,该实例常用的方法、属性有:

  • StandardName​、DaylightName

    获取 标准时区 名称、获取 夏时制时区 名称。

    TimeZone zone = TimeZone.CurrentTimeZone;
    zone.StandardName.Dump ("StandardName");    // 中国标准时间
    zone.DaylightName.Dump ("DaylightName");    // 中国夏令时
    
  • IsDaylightSavingTime

    传入 DateTime ​实例,判断 当前日期是否为夏令时

    DateTime dt1 = new DateTime (2008, 1, 1);
    DateTime dt2 = new DateTime (2008, 6, 1);
    zone.IsDaylightSavingTime (dt1).Dump ("IsDaylightSavingTime (January)");    // True
    zone.IsDaylightSavingTime (dt2).Dump ("IsDaylightSavingTime (June)");    // False
    
  • GetUtcOffset

    返回指定本地时间的 协调世界时 (UTC) 偏移量 。示例以加利福尼亚时间为准:

    zone.GetUtcOffset (dt1).Dump ("UTC Offset (January)");  // 08:00:00
    zone.GetUtcOffset (dt2).Dump ("UTC Offset (June)");     // 09:00:00
    
  • GetDaylightChanges

    获取 指定年份的夏令时信息 。示例以加利福尼亚时间为准:

    DaylightTime day = zone.GetDaylightChanges (2010);
    day.Start.Dump ("day.Start");    // 08 March
    day.End.Dump ("day.End");        // 01 November
    day.Delta.Dump ("day.Delta");    // 01:00:00
    

6.3.3.2 TimeZoneInfo

TimeZoneInfo ​与 TimeZone ​相似的属性、方法有:

  • TimeZoneInfo.Local

    相当于 TimeZone.CurrentTimeZone

  • StandardName​、DaylightName

  • IsDaylightSavingTime

    可接受 DateTime​ 实例

  • GetUtcOffset

    可接受 DateTime ​实例

下面将介绍与 TimeZone ​不同的方法、属性

TimeZoneInfo.FindSystemTimeZoneById()

该方法根据 时区 ID 获得任意时区的 TimeZoneInfo​​,使用方法如下:

TimeZoneInfo wa = TimeZoneInfo.FindSystemTimeZoneById ("W. Australia Standard Time");

Console.WriteLine (wa.Id);                   // W. Australia Standard Time
Console.WriteLine (wa.DisplayName);          // (GMT+08:00) Perth
Console.WriteLine (wa.BaseUtcOffset);        // 08:00:00
Console.WriteLine (wa.SupportsDaylightSavingTime);     // True
TimeZoneInfo.GetSystemTimeZones()

该静态方法返回 全世界所有的时区

foreach (TimeZoneInfo z in TimeZoneInfo.GetSystemTimeZones())
    Console.WriteLine (z.Id);

C7.0 核心技术指南 第7版.pdf - p290 - C7.0 核心技术指南 第 7 版-P290-20240207142423

TimeZoneInfo.ConvertTime()

该静态方法将 DateTime​​ 或 DateTimeOffset​​ 从 一个时区 转换到 另一个时区 。若想要直接与 UTC 时间互转,可以使用 ConvertTimeFromUtc() ​​ 和 ConvertTimeToUtc() ​​。

夏令时的处理方法

太平洋时间的夏令时规则如下:夏令时开始于每年三月的第二个星期日凌晨 2 点,此时钟表会调快一小时变为 3 点,减少那一天的时长一小时(即 02:00:00~03:00:00 消失)。夏令时结束于每年十一月的第一个星期日凌晨 2 点,此时钟表会调慢一小时变为 1 点,使那一天增加一小时。这意味着,在夏令时期间,夜晚到来的时间会更晚,以便利用更多的日光时间。

太平洋时间的夏令时结束于每年十一月的第一个星期日凌晨 2 点,此时钟表会调慢一小时变为 1 点,使那一天增加一小时(即 01:00:00~02:00:00 发生重复)。这意味着在夏令时结束时,人们会经历一个“额外”的小时,通常用于多睡一个小时。这个调整标志着回到标准时间的转变,为即将到来的冬季做准备。

TimeZoneInfo ​专门提供了处理夏令时的方法,罗列如下:

  1. IsInvalidTime()​​

    DateTime​​ 是夏令时时钟的无效时间(即凌晨 2 点至凌晨 3 点),返回 true​​。

  2. IsAmbiguousTime()​​

    DateTime​​ 或 DateTimeoffset​​ 是夏令时结束的重复时间(即凌晨 1 点至凌晨 2 点),返回 true​​。

  3. GetAmbiguousTimeOffsets()

    DateTime ​或者 DateTimeOffset​ 表示的时间为夏令时结束的重复时间,该方法会返回 TimeSpan ​数组,表示可能的 UTC 偏移量。

TimeZoneInfo​ 获取夏令时起止日期

TimeZoneInfo 获取夏令时起止日期非常复杂,此处不做赘述。

6.3.4 夏令时与 DateTime

如果使用 DateTimeOffset​ ​或 UTCDateTime​ ​进行相等比较,那么结果 不受 夏令时影响。对于本地时间的 DateTime​ ​则 可能会受到 夏令时的影响。

这些规则可以总结为:

  • 夏令时只影响 本地 时间,而不影响 UTC 时间。
  • 当且仅当使用本地时间的 DateTime​ ​时,当夏令时的时钟回调时,基于时间前移的比较将得到错误的结果。
  • 即使时钟回调了,也总是可以可靠地将本地时间转换为 UTC 时间,反之亦然。

夏令时的时间进行比较时,建议使用 ToUniversalTime ​ 将本地时间转换为 UTC 时间再进行。

C7.0 核心技术指南 第7版.pdf - p294 - C7.0 核心技术指南 第 7 版-P294-20240207191838

IsDaylightSavingTime()

当实例为 DateTimeOffset ​ ​类型,或实例 DateTime.Kind​ ​为 UTC ,该方法始终返回 false​​。

6.4 格式化和解析

6.4.1 ToString​ 和 Parse

所有简单的值类型的 ToString​ 方法都能产生有意义的字符串输出。同时这些类型都定义了静态的 Parse​ 方法来反向转换,转换失败则会抛出 FormatException ​。

此外,DateTime(Offset) ​和数字类型的 Parse ​和 TryParse ​方法会使用 本地的文化设置 。我们可以通过两种方式解决:

  1. 指定一个 CultureInfo ​对象覆盖本地文化设置;
  2. 转换时指定一个 不变文化

例如在德国,“.”表示千位分隔符而非小数点。可以通过指定一个 不变文化(CultureInfo.InvariantCulture 解决上述问题:

Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo ("de-DE");  // Germany
double.Parse ("1.234").Dump ("Parsing 1.234");   // 1234 
(1.234).ToString ().Dump ("1.234.ToString()");   // 1,234

// Specifying invariant culture fixes this:
double.Parse ("1.234", CultureInfo.InvariantCulture).Dump ("Parsing 1.234 Invariantly");    // 1.234
(1.234).ToString (CultureInfo.InvariantCulture).Dump ("1.234.ToString Invariant");          // 1.234

6.4.2 格式提供器

所有 数字 类型以及 DateTime(offset) ​ 类型都实现了 IFormattable​ 接口,该接口定义如下:

public interface IFormattable
{
	string ToString(string format, IFormatProvider formatProvider);
}
IFormattable

IFormattable​ 接口方法的第一个参数是 格式字符串(format string) ,用于提供指令;第二个参数是 格式提供器(IFormatProvider ,决定指令将如何转换。例如:

NumberFormatInfo formatInfo = new NumberFormatInfo();
f.CurrencySymbol = "$$";
Console.WriteLine (3.ToString ("C", formatInfo));     // $$ 3.00

这里的“C”是表示货币的格式字符串,而 NumberFormatInfo​ ​对象是一个 格式提供器 ,它决定了货币(和其他数字形式)该如何展示。这个机制也支持全球化。

默认格式提供器(IFormatProvider​)

如果格式字符串(format string)或格式提供器(IFormatProvider​)为 null,则使用默认格式提供器( CultureInfo.CurrentCulture ​),它的默认值由控制面板设置决定。

方便起见,大多数类型都重载了 ToString ​方法,调用无参 ToString ​方法,相当于直接使用 默认 格式提供器,格式字符串为 空字符串

// 使用空字符串,默认格式提供器。以下代码等价
Console.WriteLine (10.3.ToString ("", null));
Console.WriteLine (10.3.ToString ());
// 输出结果相同
Console.WriteLine (10.3.ToString ("C", null));
Console.WriteLine (10.3.ToString ("C"));

.NET Framework 定义了以下三种格式提供器(它们都实现了 IFormatProvider​):

  1. NumberFormatInfo
  2. DateTimeFormatInfo
  3. CultureInfo

6.4.2.1 格式提供器和 CultureInfo

不变文化总是保持相同的设置,和计算机设置无关:

DateTime dt = new DateTime(2000, 1, 2);
CultureInfo iv = CultureInfo.InvariantCulture;
Console.WriteLine(3.ToString("C", iv));      // ¤3.00
Console.WriteLine (dt.ToString (iv));        // 01/02/2000 00:00:00
Console.WriteLine (dt.ToString ("d", iv));   // 01/02/2000

C7.0 核心技术指南 第7版.pdf - p297 - C7.0 核心技术指南 第 7 版-P297-20240207231306

6.4.2.2 使用 NumberFormatInfo​ 和 DateTimeFormatInfo

通过构造器得到的 NumberFormatInfo​、DateTimeFormatInfo ​实例,其初始设置基于不变文化。若想以其他文化做为起点,可以 Clone 一个现有的格式提供器:

NumberFormatInfo f2 = (NumberFormatInfo) CultureInfo.CurrentCulture.NumberFormat.Clone();

// Now we can edit f2:
f2.NumberGroupSeparator = "*";
Console.WriteLine (12345.6789.ToString ("N3", f2));   // 12*345.679

原生格式提供器(CultureInfo.CurrentCulture.NumberFormat​)为只读的,克隆得到的实例是可写的

6.4.2.3 组合格式化

String.Format​、Console.Write(Line)​、StringBuilder.AppendFormat​ 等方法都可以接受组合格式字符串,例如:

string composite = "Credit={0:C}";
Console.WriteLine (string.Format (composite, 500));   // Credit=$500.00
// 可简化为
Console.WriteLine ("Credit={0:C}", 500);   // Credit=$500.00

不过,String.Format ​还可以接受一个** 格式提供器 **:

object someObject = DateTime.Now;
string s = string.Format (CultureInfo.InvariantCulture, "{0}", someObject);

这段代码等价于:

object someObject = DateTime.Now;
string s;
if (someObject is IFormattable)
	s = ((IFormattable)someObject).ToString (null, CultureInfo.InvariantCulture);
else if (someObject == null)
	s = "";
else
	s = someObject.ToString();

6.4.2.5 IFormatProvider ​​ 和 ICustomFormatter ​​

自定义格式提供器,需要实现 IFormatProvider ​​、 ICustomeFormatter ​ ​接口:

public interface IFormatProvider
{
	object? GetFormat(Type? formatType);
}
public interface ICustomFormatter
{
	string Format(string? format, object? arg, IFormatProvider? formatProvider);
}

其中 ICustomFormatter.Format ​方法负责 执行格式化IFormatProvider.GetFormat ​方法返回 格式器(即自定义类型)

static void Main()
{
	double n = -123.45;
	IFormatProvider fp = new WordyFormatProvider();
	Console.WriteLine(string.Format(fp, "{0:C} in words is {0:W}", n));
}

public class WordyFormatProvider : IFormatProvider
{
	public object GetFormat (Type formatType)
	{
		if (formatType == typeof (ICustomFormatter)) return new WordFormatter();
		return null;
	}
}

public class WordFormatter : ICustomFormatter
{
	static readonly string[] _numberWords =
		"zero one two three four five six seven eight nine minus point".Split();

	IFormatProvider _parent;   // Allows consumers to chain format providers

	public WordFormatter() : this(CultureInfo.CurrentCulture) { }
	public WordFormatter(IFormatProvider parent)
	{
		_parent = parent;
	}

	public string Format(string format, object arg, IFormatProvider prov)
	{
		// If it's not our format string, defer to the parent provider:
		if (arg == null || format != "W")
			return string.Format(_parent, "{0:" + format + "}", arg);

		StringBuilder result = new StringBuilder();
		string digitList = string.Format(CultureInfo.InvariantCulture, "{0}", arg);
		foreach (char digit in digitList)
		{
			int i = "0123456789-.".IndexOf(digit);
			if (i == -1) continue;
			if (result.Length > 0) result.Append(' ');
			result.Append(_numberWords[i]);
		}
		return result.ToString();
	}
}

Notice

上述代码和书中代码不同,我将 IFormatProvider​ 和 ICustomFormatter​ 分成了两个类,便于理解。原书代码为:

static void Main()
{
	double n = -123.45;
	IFormatProvider fp = new WordyFormatProvider();
	Console.WriteLine (string.Format (fp, "{0:C} in words is {0:W}", n));
}

public class WordyFormatProvider : IFormatProvider, ICustomFormatter
{
	static readonly string[] _numberWords =
		"zero one two three four five six seven eight nine minus point".Split();

	IFormatProvider _parent;   // Allows consumers to chain format providers

	public WordyFormatProvider () : this (CultureInfo.CurrentCulture) { }
	public WordyFormatProvider (IFormatProvider parent)
	{
		_parent = parent;
	}

	public object GetFormat (Type formatType)
	{
		if (formatType == typeof (ICustomFormatter)) return this;
		return null;
	}

	public string Format (string format, object arg, IFormatProvider prov)
	{
		// If it's not our format string, defer to the parent provider:
		if (arg == null || format != "W")
		return string.Format (_parent, "{0:" + format + "}", arg);

		StringBuilder result = new StringBuilder();
		string digitList = string.Format (CultureInfo.InvariantCulture, "{0}", arg);
		foreach (char digit in digitList)
		{
			int i = "0123456789-.".IndexOf (digit);
			if (i == -1) continue;
			if (result.Length > 0) result.Append (' ');
			result.Append (_numberWords[i]);
		}
		return result.ToString();
	}
}

6.5 标准格式字符串与解析标记

6.5.1 数字格式字符串

C7.0 核心技术指南 第7版.pdf - p301 - C7.0 核心技术指南 第 7 版-P301-20240208161024

如果不提供数值格式字符串(或者使用 null 空字符串),那么相当于使用不带数字的“G”标准格式字符串。这包括了以下两种形式:

  1. 小于 \(10^{-4}\) 或大于该类型精度的数字将表示为指数形式(科学记数法)。
  2. float ​和 double ​精度限制的两位小数是经过舍入的,以避免从二进制形式转换为十进制时的内在不精确性。

C7.0 核心技术指南 第7版.pdf - p303 - C7.0 核心技术指南 第 7 版-P303-20240208161813

6.5.2 NumberStyles

数字类型都定义了一个静态 Parse ​方法,接受 NumberStyles​(标记枚举)参数,它决定了字符串转换为数字的读取方式。

它有如下枚举成员:

// 允许字符串前、后有空白字符
AllowLeadingWhite = 1,
AllowTrailingWhite = 2,
// 允许字符串前、后有正负号
AllowLeadingSign = 4,
AllowTrailingSign = 8,
// 允许使用括号表示负数(财会常用)
AllowParentheses = 0x10,
// 允许字符串有小数点
AllowDecimalPoint = 0x20,
// 允许字符串包含千分位分隔符
AllowThousands = 0x40,
// 允许字符串使用科学计数法
AllowExponent = 0x80,
// 允许字符串包含货币符号
AllowCurrencySymbol = 0x100,
// 指定字符串表示16进制数字
AllowHexSpecifier = 0x200,
// 允许字符串表示二进制数字
AllowBinarySpecifier = 0x400,

这些枚举成员组合后构成了常用的几个值:

None = 0,
Integer = 7,
HexNumber = 0x203,
BinaryNumber = 0x403,
Number = 0x6F,
Float = 0xA7,
Currency = 0x17F,
Any = 0x1FF

Parse ​方法默认使用这些常用值。

imageimage

若不想使用默认值,需要显式指定 NumberStyles​:

int thousand = int.Parse ("3E8", NumberStyles.HexNumber);
int minusTwo = int.Parse ("(2)", NumberStyles.Integer | NumberStyles.AllowParentheses);
double.Parse ("1,000,000", NumberStyles.Any).Dump ("million");
decimal.Parse ("3e6", NumberStyles.Any).Dump ("3 million");
decimal.Parse ("$5.20", NumberStyles.Currency).Dump ("5.2");

因为我们没有指定格式提供器,因此这个例子支持本地货币符号、分组符号、小数点等。下一个例子以硬编码的方式使用欧元符号和空格分组符号来表示货币:

NumberFormatInfo ni = new NumberFormatInfo();
ni.CurrencySymbol = "€";
ni.CurrencyGroupSeparator = " ";
double.Parse ("€1 000 000", NumberStyles.Currency, ni).Dump ("million");

6.5.3 DateTime 格式字符串

DateTime​、DateTimeOffset​ 常用的格式字符串有:

C7.0 核心技术指南 第7版.pdf - p305 - C7.0 核心技术指南 第 7 版-P305-20240208172527

C7.0 核心技术指南 第7版.pdf - p305 - C7.0 核心技术指南 第 7 版-P305-20240208172548

"x"、"R"和"u"格式字符串会添加一个表示 UTC 的后缀,但是它们不能将一个本地时间自动转换为 UTC 时间(所以必须自行进行转换)。奇怪的是,"U"会自动转换为 UTC,但是它不会添加时区后缀!事实上,"o"是上述分类符中唯一一个不需要干预就能够产生一个明确的 DateTime ​的格式分类符。

DateTime​ 的解析与误解析

解析日期时,“月/日/年”和“日/月/年”很容易混淆。避免该问题有两种方式:

  1. 格式化、解析时 总是显式指定相同的 CultureInfo ​​( CultureInfo.InvariantCulture ​====)。
  2. 格式化、解析时 使用文化无关的方式(如格式字符串“o”)

此处更推荐第二种。

这里说明:

  • 格式字符串可以和 CultureInfo ​一起搭配使用

    例如,格式字符串“C”和 CultureInfo​ 共同决定输出货币的形式。

  • 部分格式字符串将使 CultureInfo​ 参数失效

    例如,使用格式字符串“o”,将忽略 CultureInfo​ 的作用。

Parse​ 和 ParseExact​ 区别
  • Parse​ 方法 式接受日期格式“o”和格式化提供器 CurrentCulture​​:

    DateTime dt2 = DateTime.Parse (s);
    
  • ParseExact​ 要求 式提供日期格式和格式化提供器:

    DateTime dt1 = DateTime.ParseExact (s, "o", null);
    

C7.0 核心技术指南 第7版.pdf - p306 - C7.0 核心技术指南 第 7 版-P306-20240209091429

6.5.4 DateTimeStyles

DateTimeStyles​​ 为标记枚举,用于 DateTime(Offset).Parse ​ ​方法。以下是枚举成员:

None = 0,
AllowLeadingWhite = 1,
AllowTrailingWhite = 2,
AllowInnerWhite = 4,
AllowWhiteSpaces = AllowLeadingWhite | AllowTrailingWhite | AllowInnerWhite,
// 解析时分秒字符串,年月日是否使用当前时间
NoCurrentDateDefault = 8,
// 参照字符串时区信息,将输出的日期转换为UTC时间
AdjustToUniversal = 0x10,
// AssumeLocal、AssumeUniversal 用于字符串没有时区信息
AssumeLocal = 0x20,
AssumeUniversal = 0x40,
RoundtripKind = 0x80

6.6 其他转换机制

6.6.1 Convert​ 类

.NET Framework 将以下类型称为基本类型:

  • bool​、char​、string​、System.DateTime​ ​和 System.DateTimeOffset
  • 所有的 C# 数字类型

Convert​ 类可以对上述数据进行转换。

C7.0 核心技术指南 第7版.pdf - p308 - C7.0 核心技术指南 第 7 版-P308-20240209094355

6.6.1.1 实数到实数的舍入转换

Convert​ 类的数字转换采用 银行家 舍入方式,避免截断产生不符合要求的数据。

double d = 3.9;
int i = Convert.ToInt32 (d);  // i == 4

如果银行舍入方式不适用,可以使用 Math.Round​ 方法,该方法可以使用额外参数控制中间值舍入方式。

银行家舍入方式,又称为“四舍六入五考虑”,是一种特殊的四舍五入规则,其核心原则如下:

  • 四舍六入:当舍去位的数值小于 5 时直接舍去;大于等于 6 时,则在舍去该位的同时向前一位进一。

  • 五考虑:当舍去位的数值等于 5 时,需要考虑五后面的数字以及五前面的数字:

    • 五后非零就进一:如果 5 后面有非零数字,则向前一位进一。

    • 五后为零看奇偶

      • 如果 5 后面没有数字(即 5 是最后一位),则看 5 前面的数字:

        • 如果 5 前面的数字为偶数,则直接舍去 5。
        • 如果 5 前面的数字为奇数,则向前一位进一。

这种舍入方式的优点是减少了在大量计算时舍入误差积累的偏差,使得结果更加公平和中性。银行家舍入法广泛应用于财务计算、统计学以及计算机科学中,特别是在浮点数运算和金融软件开发中,它有助于确保在四舍五入时整体上保持数值的准确性和公平性。

6.6.1.2 解析二、八、十六进制

ToXXX​ 方法包括一些重载方法,可以将字符串解析为其他进制:

int thirty = Convert.ToInt32  ("1E", 16);    // Parse in hexadecimal
uint five  = Convert.ToUInt32 ("101", 2);    // Parse in binary

第二个参数指定的进制必须是 281016 进制。

6.6.1.3 动态转换

Convert.ChangeType() ​ 方法可以进行动态转换:

Type targetType = typeof (int);
object source = "42";

object result = Convert.ChangeType(source, targetType);

其中 source​ 和 targetType​ 必须是 基本 类型。

上述方法的用途之一是编写可以处理多种类型的 反序列化器 。它还能够将任意 枚举 类型转换为对应的整数类型。

6.6.1.4 Base64 转换

Base64 使用 ASCII 字符集中的 64 个字符将二进制数据编码为可读的字符。

可以使用 Convert.ToBase64String ​将字节数组转换为 Base64 格式,使用 Convert.FromBase64String ​执行相反的操作。

6.6.2 XmlConvert

DateTime​ 转换

XmlConvert​ 的方法不需要提供特殊的格式字符串就能够处理 XML 格式的细微差别。

其格式化方法均为重载的 ToString​ 方法,解析方法为 ToBoolean​、ToDateTime​ 等:

string s = XmlConvert.ToString (true);    // s = "true",而非"True"
XmlConvert.ToBoolean (s).Dump();

DateTime​ 转换

XmlConvert.ToString(DateTime)​ 和 XmlConvert.ToDateTime()​(格式化和解析)方法可以接受 XmlDateTimeSerializationMode ​ 枚举参数,其元素为:

// 字符串为Local时区信息,转化/解析得到的时间为Local时间
Local,
// 字符串无时区信息,转化/解析得到的时间为UTC时间
Utc,
// 字符串无时区信息
Unspecified,
// 保持DateTime的原DateTimeKind
RoundtripKind

6.6.3 类型转换器

类型转换器用于解析 XAML。

所有类型转换器都继承自 TypeConverter ​,获取转换器需要通过 TypeDescriptor ​ 类的 GetConverter ​ 方法,方式如下:

TypeConverter cc = TypeDescriptor.GetConverter (typeof (Color));

TypeConverter​ ​常用的方法有 ConvertToString ​ ​和 ConvertFromString ​​:

Color beige  = (Color) cc.ConvertFromString ("Beige");
Color purple = (Color) cc.ConvertFromString ("#800080");
Color window = (Color) cc.ConvertFromString ("Window");

按照惯例,类型转换器的名称应以 Converter ​结尾,并且通常与它们转换的类型位于同一个命名空间中。类型是通过 TypeConverterAttribute ​与转换器联系在一起的。这样设计器就可以自动获得对应的转换器了。

类型转换器还可以提供一些设计时的服务,例如为设计器生成标准的下拉列表项,或者辅助代码序列化。

6.6.4 BitConverter

decimal​ 和 DateTime(Offset)​ 类型外,其他基本类型都可以通过 BitConverter.GetBytes ​ 转换为字节数组:

foreach (byte b in BitConverter.GetBytes (3.5))
    Console.Write (b + " ");                  // 0 0 0 0 0 0 12 64

BitConverter​ 还提供了将字节数组反向解析的方法,如 BitConverter.ToDouble

Warn

BitConverter​ 也不支持 string

BitConverter​ 和 decimal

BitConverter ​不支持 decimal​。可以通过 decimal.GetBits ​方法得到相似的结果。

decimal.GetBits​ 方法返回 int数组 ,且 decimal​ 也有接受 int数组 的构造器。

BitConverter​ 和 DateTime(Offset)

BitConverter​ 不支持 DateTime(Offset)​,但是可以通过 DateTime.ToBinary ​ 方法得到日期对应的 long ​,再通过 BitConverter.GetBytes ​ 得到对应数组。

解析时通过 BitConverter.ToLong ​ 和 DateTime.FromBinary ​ 方法进行。

DateTime(long ticks)​ 和 DateTime.FromBinary(long dateData)​ 的区别

  • new DateTime(long ticks)​ 直接根据刻度数创建日期时间

    0001 年 1 月 1 日午夜 12:00:00 以来所经过的时间以 100 纳秒为单位的刻度数(Ticks)

  • DateTime.FromBinary(long value)​ 从包含额外日期时间类型信息的二进制值重建 DateTime​ 实例。

    可以准确地还原由 DateTime​ 实例转换成二进制表示的日期和时间,包括它的 Kind​ 属性。这使得通过 FromBinary​ 方法重建的 DateTime​ 实例能够保留原实例的本地或 UTC 时间信息。

总结:

  • System.Convert

    适用于基本数据类型之间的通用转换, 接受 IFormatableProvider ​进行本地化。

  • System.Xml.XmlConvert

    专注于 XML 数据与 .NET 数据类型之间的转换,确保数据的正确格式化和编码。 不接受 IFormatableProvider ​参数。

  • System.BitConverter

    用于基本数据类型和它们的字节表示之间的转换,适用于需要处理二进制数据的场景。 不接受 IFormatableProvider ​参数,不支持 decimal ​、 DateTime(Offset) ​ 类型。

6.7 全球化

全球化专注于三个任务(重要性从大到小):

  1. 保证程序在其他文化环境中运行时不会出错。
  2. 采用一种本地文化的格式化规则,例如日期的显示。
  3. 设计程序,使之能够从将来编写和部署的附属程序集中读取文化相关的数据和字符串。

6.7.1 全球化检查清单

以下是一些必要任务的总结:

  • 认识 Unicode 和文本编码
  • 要记住 char​ 和 string​ 的一些方法是文化相关的,如 ToUpper​ 和 ToLower​,若不区分文化,则应当使用 ToUpperInvariant ​ 和 ToLowerInvariant ​。
  • 推荐使用文化无关的方式对 DateTime(Offset) ​ 进行格式化和解析。例如 ToString("o") ​以及 XmlConvert​。
  • 除非希望使用本地文化行为,否则请在格式化和解析数字或日期/时间时指定一个文化。

6.7.2 测试

可以通过 Thread.CurrentCulture​ 属性来模拟不同文化:

Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("tr-TR");

这里推荐模拟土耳其文化进行测试,原因如下:

  1. "i".ToUpper() != "I"​,且 "I".ToLower() != "i"​。
  2. 日期使用“日.月.年”的方式进行格式化,分隔符为“.​”。
  3. 小数点符号为“逗号”,而非"点"。

可以通过修改控制面板中的数字、日期格式进行测试,这将影响默认文化设置(CultureInfo.CurrentCulture​)。

imageimage

Info

更多内容见18.6.4.2 测试附属程序集、18.6.5 文化和子文化

6.8 操作数字

6.8.2 Math

Math 中定义的舍入方法有 4 个:

  1. Math.Round

    可以指定舍入位数、如何处理中间值

  2. Math.Truncate

    丢弃小数部分

  3. Math.Floor

    向下舍入

  4. Math.Ceiling

    向上舍入

Eureka

Math.Truncate​ 和 Math.Floor​ 看起来功能相同,但是面对 负数 他们的行为将出现大不同!

6.8.3 BigInteger

BigInteger​ 可以表示任意大的整数而不会丢失精度。它有如下特点:

  1. 可以从任意整数类型 式转换为 BigInteger

    BigInteger twentyFive = 25;      // implicit cast from integer
    
  2. 可以通过其 静态 方法表示更大的数字

    例如使用 BigInteger.Pow​​ 方法:

    BigInteger googol = BigInteger.Pow (10, 100); 
    

    通过 Parse ​ 方法创建大数:

    BigInteger googolFromString = BigInteger.Parse ("1".PadRight (100, '0'));
    
  3. 可以通过 字节数组 创建大数:

    RandomNumberGenerator rand = RandomNumberGenerator.Create();
    byte[] bytes = new byte [32];
    rand.GetBytes (bytes);
    var bigRandomNumber = new BigInteger (bytes);   // Convert to BigInteger
    
  4. ToString​​方法将打印所有数字

基础数值类型和 BigInteger ​可以显式相互转换,但可能 损失精度

double g1 = 1e100;                  // implicit cast
BigInteger g2 = (BigInteger) g1;    // explicit cast
g2.Dump();    // 输出:10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104

6.8.4 Complex​(复数)

Complex​ 实例化方式如下:

var c1 = new Complex (2, 3.5);
var c2 = new Complex (3, 0);

Complex​ 类型有如下特点:

  1. 标准数值类型可以 式转换为 Complex ​类型

  2. 可以通过属性访问实部和虚部

    1. Complex.Real
    2. Complex.Imaginary
    3. Complex.Phase相位角
    4. Complex.Magnitude
  3. 可以通过 相位角 来构建 Complex

    Complex c3 = Complex.FromPolarCoordinates (1.3, 5);
    
  4. 可以使用标准运算符进行运算

    Console.WriteLine (c1 + c2);    // (5, 3.5)
    Console.WriteLine (c1 * c2);    // (6, 10.5)
    
  5. 一些高级静态方法

    1. 三角函数
    2. 取对数和求幂
    3. Conjugate(求共轭复数)
    Complex.Atan (c1).Dump ("Atan");
    Complex.Log10 (c1).Dump ("Log10");
    Complex.Conjugate (c1).Dump ("Conjugate");
    

6.8.5 Random

Random​ 类能够生成类型为 byte ​、 integer ​、 double ​的伪随机数序列。若不指定种子参数,则将用当前 系统时间 来生成种子。

C7.0 核心技术指南 第7版.pdf - p316 - C7.0 核心技术指南 第 7 版-P316-20240210093951

RandomNumberGenerator

Random ​的随机性不够高,.NET Framework 提供了一种密码强度的随机数生成器 RandomNumberGenerator ​。使用方式如下:

var rand = System.Security.Cryptography.RandomNumberGenerator.Create();
byte[] bytes = new byte [4];
rand.GetBytes (bytes);       // Fill the byte array with random numbers.

BitConverter.ToInt32 (bytes, 0).Dump ("A cryptographically strong random integer");

该生成器通过 填充字节数组 产生随机数,不够灵活,要通过 BitConverter ​ 获取相应的随机数。

6.9 枚举

枚举隐式派生自 System.Enum ​ 类型,因此都可以隐式转换为 System.Enum ​ 实例:

enum Nut  { Walnut, Hazelnut, Macadamia }
enum Size { Small, Medium, Large }

static void Main()
{
	Display (Nut.Macadamia);     // Nut.Macadamia
	Display (Size.Large);        // Size.Large
}

static void Display (Enum value)		// The Enum type unifies all enums
{
	Console.WriteLine (value.GetType().Name + "." + value.ToString());
}

6.9.1 枚举值转换

6.9.1.1 将枚举转换为整数

对于 System.Enum​ 实例,将枚举值转换为整数有 4 种方式:

方式一:先将实例转换为 object ​ 再进行:
static int GetIntegralValue (Enum anyEnum) 
	=> (int) (object) anyEnum;

该方法也有缺陷,当 anyEnum ​对应的是 long ​类型,上述转化将抛出 InvalidCastException​。

方式二:使用 Convert.ToDecimal
static decimal GetAnyIntegralValue (Enum anyEnum) 
	=> Convert.ToDecimal (anyEnum);

此处用到了任何整形都可以转换为 decimal​ 的特点。

Warn

此处不能是如下代码,否则也会抛出 InvalidCastException​:

(decimal)(object)anyEnum;
方法三:使用 Convert.ChangeType
static object GetBoxedIntegralValue (Enum anyEnum)
{
	Type integralType = Enum.GetUnderlyingType(anyEnum.GetType());
	return Convert.ChangeType(anyEnum, integralType);
}

此处先用 Enum.GetUnderlyingType ​获取 enum ​的整数类型,再使用 Convert.ChangeType ​进行转化。该方法会保持原始的整数类型。

C7.0 核心技术指南 第7版.pdf - p318 - C7.0 核心技术指南 第 7 版-P318-20240210104748

方法四:调用 Format ​或 ToString ​方法

注意,需要指定格式字符串“ D”或“d ”,否则将得到 枚举对应的字符串 而非数字。

static string GetIntegralValueAsString (Enum anyEnum) 
	=> anyEnum.ToString ("D");      // returns something like "4"

这种方式在编写自定义的序列化器时很有用。

6.9.1.2 将整数转换为枚举

Enum.ToObject ​ 方法将整数值转换为一个给定类型的 enum 实例:

[Flags] public enum BorderSides { Left=1, Right=2, Top=4, Bottom=8 }

static void Main()
{
	object bs = Enum.ToObject (typeof (BorderSides), 3);
	Console.WriteLine (bs);                              // Left, Right

	// This is the dynamic equivalent of this:
	BorderSides bs2 = (BorderSides) 3;
}

6.9.1.3 字符串转换

将 enum 转换为字符串,可以调用静态的 Enum.Format ​或实例的 ToString ​方法。可用的格式字符串有:

格式字符串 行为
G 默认格式化行为,输出枚举名称
D 输出对应整数值
X 输出对应整数值(16 进制)
F 非标记枚举也按照标记枚举的方式格式化

Enum.Parse ​ 方法可以将一个字符串转换为 enum,使用方式如下:

BorderSides leftRight = (BorderSides) Enum.Parse (typeof (BorderSides), "Left, Right");

BorderSides leftRightCaseInsensitive = (BorderSides) 
        Enum.Parse (typeof (BorderSides), "left, right", true);

第三个可选参数选择是否执行大小写不敏感的解析,如果成员不存在则抛出 ArgumentException​。

6.9.2 列举枚举值

Enum.GetValues ​ 方法用于获取枚举中的所有成员(包括组合成员):

foreach (Enum value in Enum.GetValues (typeof (BorderSides)))
    Console.WriteLine (value);

Enum.GetNames ​ 执行相同的操作,但返回的是一个字符串数组。

C7.0 核心技术指南 第7版.pdf - p320 - C7.0 核心技术指南 第 7 版-P320-20240210111851

6.10 Guid​ 结构体

Guid​ 可以表示的值总共有 \(2^{128}\) 或 \(3.4×10^{18}\) 个。获取 Guid ​方法有二:

  1. 通过 Guid.NewGuid ​ ​方法创建。

  2. 通过构造器实例化。

    常用的构造器为:

    1. public Guid (byte[] b);

      需传入长度为 16byte​ 数组,可以配合 Guid.ToByteArray ​方法一起使用。

    2. public Guid (string g);

      需传入 Guid ​形式的字符串,该字符串可以放在圆括号或花括号中:

      var g1 = new Guid("7e33a841-b392-40b5-a8c4-de90eac5da7d");
      var g2 = new Guid("{7e33a841-b392-40b5-a8c4-de90eac5da7d}");
      var g3 = new Guid("(7e33a841-b392-40b5-a8c4-de90eac5da7d)");
      var g4 = new Guid("7e33a841b39240b5a8c4de90eac5da7d");
      

Guid​ 有如下特点:

  1. 当以字符串形式出现的时候,Guid ​是一个由 32十六 进制数字表示的值,
  2. Guid​ ​是一个 结构体 ,支持 类型的语义,因而前面的例子可以使用相等运算符。
  3. Guid​ ​的 ToByteArray​ ​方法可以将其转换为一个字节数组。
  4. Guid.Empty ​​ 静态属性将返回一个空的 Guid​​(全部为 ),它通常用来表示 null ​​。

6.11 相等比较

6.11.2 标准等值比较协议

6.11.2.1 == 和 !=

== ​和 !=​ 运算符执行静态解析(编译时确定执行方法),如下两种比较绑定的方法不同,结果不同:

如下代码将输出 True ​:

int x = 5;
int y = 5;
Console.WriteLine(x == y);

如下代码将输出 False ​:

object x = 5;
object y = 5;
Console.WriteLine(x == y);

6.11.2.2 Object.Equals​ 虚方法

Equals​ 是在运行时根据对象的实际类型解析的。对于引用类型,Equals​ 默认进行 引用相等 比较。对于结构体,Equals​ 会 调用每一个字段的 Equals ​ 进行结构化比较。

其使用方式如下,如下代码将输出 True ​​:

object x = 5;
object y = 5;
Console.WriteLine(x.Equals(y));

6.11.2.3 object.Equals​ 静态方法

object.Equals​ 静态方法执行的操作如下:

public static bool AreEqual (object objA, object objB)
    => objA == null ? objB == null : objA.Equals (objB);

Object.Equals​​ 虚方法不同,该静态方法接受两个参数。它常用于 ==​​ 和 !=​​ 无法使用的场景( 编译 时无法确认类型的场景),譬如泛型实例比较:

class Test<T>
{
    T _value;
    public void SetValue(T newValue)
    {
        if (!object.Equals(newValue, _value))
        {
            _value = newValue;
            OnValueChanged();
        }
    }

    protected virtual void OnValueChanged() {}
}

上述代码无法使用 ==​ 和 !=​(因为类型不确定,编译时无法绑定);对于 Object.Equals​ 虚方法,如果 newValue​ 为 null,则会抛出 NullReferenceException​ 异常,因此这里使用静态方法 Object.Equals​。

C7.0 核心技术指南 第7版.pdf - p325 - C7.0 核心技术指南 第 7 版-P325-20240210160719

6.11.2.4 object.ReferenceEquals ​ 静态方法

object.ReferenceEquals ​​ 可以进行引用比较,防止 Equals​ ​方法、==​​、!=​​ 重载 导致的引用比较失效。

C7.0 核心技术指南 第7版.pdf - p326 - C7.0 核心技术指南 第 7 版-P326-20240210165127

在 C#8.0 可以通过 is 运算符判断是否引用相同,详见 Pattern Matching

6.11.2.5 IEquatable<T>​ 接口

详见 DO:值类型需要实现IEquatable

6.11.2.6 Equals​ 和 ==​ 在何时并不等价

之前提到,有时 ==​ 和 Equals​ 应用不同的相等定义是非常有用的。

常见的不等价原因有两种:

1. 数值比较保证 自反性

例如如下代码分别输出“ FalseTrue ”:

double x = double.NaN;
Console.WriteLine (x == x);
Console.WriteLine (x.Equals(x))

double ​类型的 == ​运算符强制规定一个 NaN ​不等于任何对象,即使是另一个值也是 NaN ​。这从数学角度来说是非常自然的,并且也反映了底层 CPU 的行为。然而,Equals ​方法必须支持 自反相等 ,换句话说:

x.Equals(x)​ 必须总是返回 true ​。

集合和字典需要 Equals ​保持这个行为,否则就无法找到之前存储的项目了。

2. 引用类型不同的相等含义

Equals ​和 == ​含义不同这种做法在引用类型中要多得多,开发者自定义 Equals ​实现 的相等比较,而仍旧令 == ​执行(默认的) 引用 相等比较。StringBuilder ​类就是采用了这种方式,如下代码将输出“ FalseTrue ”:

var sb1 = new StringBuilder ("foo");
var sb2 = new StringBuilder ("foo");
Console.WriteLine (sb1 == sb2);
Console.WriteLine (sb1.Equals (sb2));

6.11.3 相等比较和自定义类型

6.11.3.3 如何重写相等语义

下面是重写相等语义的步骤:

  1. 重写 GetHashCode() ​ 和 Equals()方法。
  2. (可选)重载 != ​ 和 ==
  3. (可选)实现 IEquatable<T>

6.11.3.8 范例:Area 结构体

在实现 GetHashCode​ 方法时,当类型拥有多于两个的字段,由 Josh Bloch 推荐的模式能够在结果良好的情况下同时保证性能:

int hash = 17;                            // 17 = some prime number
hash = hash * 31 + field1.GetHashCode();  // 31 = another prime number
hash = hash * 31 + field2.GetHashCode();
hash = hash * 31 + field3.GetHashCode();
...
return hash;

6.12 顺序比较

6.12.1 IComparable

IComparable​ 的定义方式如下:

public interface IComparable       { int CompareTo (object other); }
public interface IComparable<in T> { int CompareTo (T other);      }

大多数基本类型都实现了这两种 IComparable​ 接口。

IComparable​ 与 Equals

  • Equals​​ 返回 true​​ 时,CompareTo​​ 仅能返回 0
  • Equals​​ 返回 false​​ 时,CompareTo​​ 可以返回 任何结果,包括 0

换句话说,相等比较是严格的。例如:字符串"ṻ"和"ǖ"用 Equals​ 比较时是不同的,然而用 CompareTo​ 比较时则是相同的。总之,CompareTo​ 永远比不上 Equals​ 更严格。

C7.0 核心技术指南 第7版.pdf - p333 - C7.0 核心技术指南 第 7 版-P333-20240210214839

6.13 实用类

6.13.1 Console​ 类

Console.Out​ 属性、Console.SetOut​、Console.SetIn​ 可以搭配使用,重定向 Console​ 的输入和输出流:

// 保留原输出流
var oldOut = Console.Out;

// 输出至文件
using (TextWriter w = File.CreateText("D:\\output.txt"))
{
    Console.SetOut(w);
    Console.WriteLine("Hello world");
}

// 恢复原标准输出流
Console.SetOut(oldOut);

6.13.2 Environment​ 类

该类提供了很多有用的属性:

  • 文件和文件夹

    • CurrentDirectory​、SystemDirectory​、CommandLine
  • 计算机和操作系统

    • MachineName​、ProcessorCount​、OSVersion​、NewLine
  • 用户登录

    • UserName​、UserInteractive​、UserDomainName
  • 诊断信息

    • TickCount​、StackTrace​、WorkingSet​、Version
  • 设置应用程序返回值

    • ExitCode

一些方法:

  • 访问系统变量

    • GetEnvironmentVariable​、GetEnvironmentVariables​、SetEnvironmentVariable

6.13.4 AppContext​ 类

System.AppContext​​ 类提供了一个 全局的开关配置表 ,以方便消费者控制新功能的开启和关闭状态。这种隐含的方式可用于添加实验功能而大多数用户并不知道它的存在。

例如,开发者可以使用如下方式启用程序库的某个功能:

AppContext.SetSwitch ("MyLibrary.SomeBreakingChange", true);

程序库的代码则可以使用如下的方式进行相关配置的开关检查:

bool isDefined, switchValue;
isDefined = AppContext.TryGetSwitch ("MyLibrary.SomeBreakingChange", out switchValue);

TryGetSwitch ​方法将在开关未定义的情况下返回 false​。这样我们就可以区分未定义和值为 false ​这两种不同的情况了,这种区分是非常必要的。

标签:Console,string,框架,C7.0,基础,DateTime,WriteLine,字符串
From: https://www.cnblogs.com/hihaojie/p/18639809/chapter-6-framework-basis-z1ggv44

相关文章

  • 学期2024-2025-1 学号20241424 《计算机基础与程序设计》第14周学习总结
    学期2024-2025-1学号20241424《计算机基础与程序设计》第14周学习总结作业信息|这个作业属于2024-2025-1-计算机基础与程序设计)||-- |-- ||这个作业要求在2024-2025-1计算机基础与程序设计第14周作业||这个作业的目标|<学习《C语言程序设计》第12章并完成云班课测试>||作......
  • QT基础篇(鼠标事件)
    QT事件(鼠标篇)前言一、事件二、使用步骤1.查找事件2.事件的处理总结前言本文讲解众多事件中的其中一个,鼠标事件。并以此为例子学习其他事件。本文只是简单讲解,如需详细请查看别人的博客一、事件在Qt中使用⼀个对象来表示⼀个事件。所有的Qt事件均继承于抽象类QEv......
  • 学期2024-2025-1学号20241416《计算机基础与程序设计》第十四周学习总结
    作业信息这个作业属于哪个课程 <班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里 <作业要求的链接>(如2024-2025-1计算机基础与程序设计第一周作业)这个作业的目标 <写上具体方面>作业正文 https://www.cnblogs.com/manurios/p/18639504教材学习内容总结C......
  • Axure入门教程 -- 第三章:交互设计基础
    第三章:交互设计基础3.1交互原理与Axure的事件系统3.1.1什么是交互?交互设计是指为原型添加响应动作,使用户行为与页面元素产生互动。Axure支持多种交互类型,例如:•点击按钮跳转页面•鼠标悬停显示菜单•表单提交后显示提示信息3.1.2Axure事件系统简介Axure通过触......
  • 2024.12.29-1 结构体(基础)
    1)原因:一个整体需要用不同类型(intchar等等)的数据来描述,结构体是更好的来描述内容的工具。2)理解:与数组类似,为不同元素的集合体。3)定义://定义结构体。类似于模板,一般不给赋予具体的值,每一项也并不是都要使用。structStudent//开头必须大写。{   intnum;   ch......
  • 学期2024-2025-1 学号20241428 《计算机基础与程序设计》第13周学习总结
    作业信息这个作业属于哪个课程 <班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里 <作业要求的链接>(](https://i.cnblogs.com/posts/edit))这个作业的目标 《C语言程序设计》第12章并完成云班课测试教材学习内容总结文件指针:在C语言中,使用FILE类型定义文......
  • 2024-2025-1 20241409 《计算机基础与程序设计》第四周学习总结
    作业信息作业归属课程:https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP作业要求:https://www.cnblogs.com/rocedu/p/9577842.html#WEEK04作业目标:门电路;组合电路,逻辑电路;冯诺依曼结构;CPU,内存,IO管理;嵌入式系统,并行结构;物理安全作业正文:教材学习内容总结《计算机科学概......
  • 深入探讨 Nginx 性能优化:从基础到高级的最佳实践
    目录引言Nginx性能优化的意义Nginx性能优化的主要方向系统层面的优化4.1优化操作系统的文件描述符4.2调整TCP参数4.3使用高效的磁盘I/O调度器Nginx配置优化5.1优化worker进程和连接数5.2使用异步和非阻塞I/O模式5.3配置Gzip压缩5.4开启缓存和缓存控......
  • 学期 2024-2025-1 学号 20241409《计算机基础与程序设计》第十四周学习总结
    作业信息这个作业属于哪个课程 2024-2025-1-计算机基础与程序设计这个作业要求在哪里 2024-2025-1计算机基础与程序设计第十四周作业这个作业的目标 《C语言程序设计》第十四章作业正文 本周学习内容进行了缓冲区溢出实验缓冲区溢出是指程序试图向缓冲区写入超出预分配固定......
  • Java 大视界 -- Java 大数据测试框架与实践:确保数据处理质量(十二)
           ......