C#中char代表一个Unicode字符。char是System.Char的别名,System.Char定义了一系列静态方法对字符进行处理:
Console.WriteLine("quickbrownfox".Contains("brown"));//TrueConsole.WriteLine("quickbrownfox".EndsWith("fox"));//TrueConsole.WriteLine("abcde".IndexOf("cd"));//2Console.WriteLine("abcde".IndexOf("xx"));//-1Console.WriteLine("abcde".IndexOf("CD",StringComparison.CurrentCultureIgnoreCase));//2Console.WriteLine("ab,cdef".IndexOfAny(newchar[]{'',','}));//2Console.WriteLine("pas5w0rd".IndexOfAny("0123456789".ToCharArray()));//36.1.2.5字符串处理PadLeft和PadRight会用特定的字符(如果未指定则使用空格)将字符串填充为指定的长度:
Console.WriteLine("12345".PadLeft(9,'*'));//****12345Console.WriteLine("12345".PadLeft(9));//12345如果输入字符串长度大于填充长度,则返回不发生变化的原始字符串。
TrimStart和TrimEnd会从字符串的开始或结尾删除指定的字符,Trim则是从开始和结尾执行删除操作。这些方法默认会删除空白字符(包括空格、制表符、换行和这些字符的Unicode变体):
Console.WriteLine("abc\t\r\n".Trim().Length);//3
ToUpper和ToLower默认情况下会受用户当前语言设置的影响;ToUpperInvariant和ToLowerInvariant则总是应用英语字母表规则。
默认情况下,Split使用空白字符作为分隔符。Split还可以接受一个StringSplitOptions枚举值用以**删除**空项。这在一行文本中有多种单词分隔符时很有用。
Notice
默认参数下,面对连续的空白字符,Split分隔时只会移除第一个空白,剩余空白仍会保留。此时可以传入上述StringSplitOptions枚举参数。
静态方法Join则执行和Split相反的操作。它需要一个分隔符和字符串的数组:
string[]words="Thequickbrownfox".Split();stringtogether=string.Join("",words);6.1.2.7String.Format与组合格式字符串调用String.Format时,需要提供一个组合格式字符串,后面紧跟每一个嵌入的变量。
花括号中的格式项(formatitem)对应参数的位置,后面可以跟随:
最小宽度用于对齐各个列。如果其值为负数,则为左对齐,否则为右对齐,例如:
composite="Name={0,-20}CreditLimit={1,15:C}";Console.WriteLine(string.Format(composite,"Mary",500));Console.WriteLine(string.Format(composite,"Elizabeth",20000));输出:
Name=MaryCreditLimit=¥500.00Name=ElizabethCreditLimit=¥20,000.00等价于:
s="Name="+"Mary".PadRight(20)+"CreditLimit="+500.ToString("C").PadLeft(15);Tips
当我们不需要最小宽度,或不需要格式字符串,都是可以省略的。如下代码分别省略了格式字符串和最小宽度:
Atom、atom、Zamia
使用不变文化其排序结果是:
atom、Atom、Zamia
使用序列比较排序结果是:
Atom、Zamia、atom
字符串的==运算符执行的是区分大小写的**序列比较**,与不带参数的String.Equals相同。
string的Equals方法有静态版本,且推荐静态版本,因为它在字符串为null时仍然有效:
publicboolEquals(stringvalue,StringComparisoncomparisonType);publicstaticboolEquals(stringa,stringb,StringComparisoncomparisonType);StringComparison是枚举类型,其定义如下:
publicstaticintCompare(stringstrA,stringstrB,StringComparisoncomparisonType);publicstaticintCompare(stringstrA,stringstrB,boolignoreCase,CultureInfoculture);publicstaticintCompare(stringstrA,stringstrB,boolignoreCase)publicstaticintCompareOrdinal(stringstrA,stringstrB)后两个方法是前面两个方法的快捷调用形式。
StringBuilder.AppendFormat与String.Format相似,接受一个组合格式字符串。
此外,StringBuilder还有可写的**索引器**,用于获得/设置每一个字符。
StringBuilder.Length属性可写,可以将其设置为0以清除内容。
我们可以通过Encoding.GetEncoding方法获取编码:
Encodingutf8=Encoding.GetEncoding("utf-8");Encodingchinese=Encoding.GetEncoding("GB18030");也可以通过Encoding的静态属性获得相应编码:
还可以通过实例化获得相应编码。构造器有各种选项:
对于超过16位的字符,UTF-16需要两个char来表示,这会有两个问题:
如果需要支持双字字符,那么可以用char类型下的静态方法将32位代码点转换为一个包含两个字符的字符串,同样也可以进行反向转换:
stringConvertFromUtf32(intutf32);intConvertToUtf32(charhightSurrogate,charlowSurrogate);双字节字符每个字的范围在0xD800~0xDFFF,我们可以通过char的静态方法进行判断:
创建TimeSpan的方法有三种:
Console.WriteLine(newTimeSpan(2,30,0));//02:30:00Console.WriteLine(TimeSpan.FromHours(2.5));//02:30:00Console.WriteLine(TimeSpan.FromHours(-2.5));//-02:30:00Console.WriteLine(DateTime.MaxValue-DateTime.MinValue);
TimeSpan重载了<、>、+和-运算符:
(TimeSpan.FromHours(2)+TimeSpan.FromMinutes(30)).Dump("2.5hours");TimeSpan的默认值是TimeSpan.Zero
最小单位均为100纳秒,取值范围为**0001年到**9999年。
DateTime和DateTimeOffset在比较时有如下区别:
DateTime构造器允许指定一个DateTimeKind枚举,其元素如下:
DateTime默认使用公历,其构造器可以接收Calendar对象,可以使用System.Globalization下的Calendar子类来指定一个日期:
//希伯来历DateTimed=newDateTime(5767,1,1,newSystem.Globalization.HebrewCalendar());Console.WriteLine(d);//12/12/200612:00:00AMDateTime(longtick)还可以使用long类型的计数值(tick)来构造DateTime,其中计数值从01/01/0001年午夜开始计时,单位为100ns。
1.0表示1899年12月31日(即基点日期后的第一天)。
2.5表示1900年1月1日中午12点(即基点日期后的第二天,加上半天)。
DateTimeOffset和DateTime具有类似的构造器,其区别是DateTimeOffset还需要指定一个TimeSpan类型的UTC偏移量:
publicDateTimeoffset(intyear,intmonth,intday,inthour,intminute,intsecond,TimeSpanoffset);publicDateTimeoffset(intyear,intmonth,intday,inthour,intminute,intsecond,intmillisecond,TimeSpanoffset);TimeSpan必须是整数分钟数,否则构造器会抛出一个异常。
DateTime可以隐式转换为DateTimeOffset:
DateTimeOffsetdt=newDateTime(2000,2,3);6.2.2.4获取当前DateTime/DateTimeOffsetDateTime有一个Today属性,返回日期的部分:
Console.WriteLine(DateTime.Today);//2024/2/60:00:00这些方法(Now等)的返回精度取决于操作系统,一般情况下都在10~20毫秒范围内。
DateTime和DateTimeOffset都提供了如下相似的属性和方法(有删减):
另外,DateTime和DateTimeOffset实例均可进行加减操作,其背后调用的是Add()方法。
DateTime有多种格式化方式:
DateTimeOffset仅有ToString()方法。
使用“o”,则输出的是如下格式:
2024-02-06T18:01:33.1248308+08:00
而Parse/TryParse和ParseExact/TryParseExact静态方法则执行和ToString相反的操作:它们将字符串转换为DateTime(Offset)。这些方法同样进行了重载以接受格式提供器。
由于DateTime和DateTimeOffset都是结构体因此它们是不能为null。当需要将其设置为null的时候可以使用如下两种方法:
使用可空类型通常是最佳方法,因为编译器可以防止代码出现错误。DateTime.MinValue对于兼容C#2.0(引入了可空类型)之前编写的代码是很有用的。
还可以通过DateTime.SpecifyKind静态方法创建一个Kind不同的DateTime:
DateTimeOffset内部包括一个总是UTC的DateTime字段和一个用16位整数表示的以分钟计量的UTC偏移量。在比较时仅仅比较(UTC的)DateTime字段,而偏移量主要用于格式化。
如果要在比较中将Offset(偏移量)也考虑在内,可以使用EqualsExact方法,如下代码将输出“True,False”:
DateTimeOffsetlocal=DateTimeOffset.Now;DateTimeOffsetutc=local.ToUniversalTime();Console.WriteLine(local==utc);Console.WriteLine(local.EqualsExact(utc));6.3.3TimeZone和TimeZoneInfoTimeZone和TimeZoneInfo类均提供时区名称、时区的UTC偏移量和夏令时规则。
TimeZone.CurrentTimeZone静态方法会根据本地设置返回一个TimeZone实例,该实例常用的方法、属性有:
TimeZoneInfo与TimeZone相似的属性、方法有:
下面将介绍与TimeZone不同的方法、属性
该方法根据时区ID获得任意时区的TimeZoneInfo,使用方法如下:
TimeZoneInfowa=TimeZoneInfo.FindSystemTimeZoneById("W.AustraliaStandardTime");Console.WriteLine(wa.Id);//W.AustraliaStandardTimeConsole.WriteLine(wa.DisplayName);//(GMT+08:00)PerthConsole.WriteLine(wa.BaseUtcOffset);//08:00:00Console.WriteLine(wa.SupportsDaylightSavingTime);//TrueTimeZoneInfo.GetSystemTimeZones()该静态方法返回全世界所有的时区:
TimeZoneInfo专门提供了处理夏令时的方法,罗列如下:
TimeZoneInfo获取夏令时起止日期非常复杂,此处不做赘述。
这些规则可以总结为:
当实例为DateTimeOffset类型,或实例DateTime.Kind为UTC,该方法始终返回false。
所有简单的值类型的ToString方法都能产生有意义的字符串输出。同时这些类型都定义了静态的Parse方法来反向转换,转换失败则会抛出FormatException。
此外,DateTime(Offset)和数字类型的Parse和TryParse方法会使用本地的文化设置。我们可以通过两种方式解决:
例如在德国,“.”表示千位分隔符而非小数点。可以通过指定一个不变文化(CultureInfo.InvariantCulture)解决上述问题:
Thread.CurrentThread.CurrentCulture=CultureInfo.GetCultureInfo("de-DE");//Germanydouble.Parse("1.234").Dump("Parsing1.234");//1234(1.234).ToString().Dump("1.234.ToString()");//1,234//Specifyinginvariantculturefixesthis:double.Parse("1.234",CultureInfo.InvariantCulture).Dump("Parsing1.234Invariantly");//1.234(1.234).ToString(CultureInfo.InvariantCulture).Dump("1.234.ToStringInvariant");//1.2346.4.2格式提供器所有数字类型以及DateTime(offset)类型都实现了IFormattable接口,该接口定义如下:
publicinterfaceIFormattable{ stringToString(stringformat,IFormatProviderformatProvider);}IFormattableIFormattable接口方法的第一个参数是格式字符串(formatstring),用于提供指令;第二个参数是格式提供器(IFormatProvider),决定指令将如何转换。例如:
NumberFormatInfoformatInfo=newNumberFormatInfo();f.CurrencySymbol="$$";Console.WriteLine(3.ToString("C",formatInfo));//$$3.00这里的“C”是表示货币的格式字符串,而NumberFormatInfo对象是一个格式提供器,它决定了货币(和其他数字形式)该如何展示。这个机制也支持全球化。
如果格式字符串(formatstring)或格式提供器(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"));.NETFramework定义了以下三种格式提供器(它们都实现了IFormatProvider):
不变文化总是保持相同的设置,和计算机设置无关:
通过构造器得到的NumberFormatInfo、DateTimeFormatInfo实例,其初始设置基于不变文化。若想以其他文化做为起点,可以Clone一个现有的格式提供器:
NumberFormatInfof2=(NumberFormatInfo)CultureInfo.CurrentCulture.NumberFormat.Clone();//Nowwecaneditf2:f2.NumberGroupSeparator="*";Console.WriteLine(12345.6789.ToString("N3",f2));//12*345.679原生格式提供器(CultureInfo.CurrentCulture.NumberFormat)为只读的,克隆得到的实例是可写的
String.Format、Console.Write(Line)、StringBuilder.AppendFormat等方法都可以接受组合格式字符串,例如:
stringcomposite="Credit={0:C}";Console.WriteLine(string.Format(composite,500));//Credit=$500.00//可简化为Console.WriteLine("Credit={0:C}",500);//Credit=$500.00不过,String.Format还可以接受一个**格式提供器**:
objectsomeObject=DateTime.Now;strings=string.Format(CultureInfo.InvariantCulture,"{0}",someObject);这段代码等价于:
objectsomeObject=DateTime.Now;strings;if(someObjectisIFormattable) s=((IFormattable)someObject).ToString(null,CultureInfo.InvariantCulture);elseif(someObject==null) s="";else s=someObject.ToString();6.4.2.5IFormatProvider和ICustomFormatter自定义格式提供器,需要实现IFormatProvider、ICustomeFormatter接口:
publicinterfaceIFormatProvider{ objectGetFormat(TypeformatType);}publicinterfaceICustomFormatter{ stringFormat(stringformat,objectarg,IFormatProviderformatProvider);}其中ICustomFormatter.Format方法负责执行格式化,IFormatProvider.GetFormat方法返回格式器(即自定义类型):
上述代码和书中代码不同,我将IFormatProvider和ICustomFormatter分成了两个类,便于理解。原书代码为:
如果不提供数值格式字符串(或者使用null空字符串),那么相当于使用不带数字的“G”标准格式字符串。这包括了以下两种形式:
数字类型都定义了一个静态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=0x1FFParse方法默认使用这些常用值。
若不想使用默认值,需要显式指定NumberStyles:
intthousand=int.Parse("3E8",NumberStyles.HexNumber);intminusTwo=int.Parse("(2)",NumberStyles.Integer|NumberStyles.AllowParentheses);double.Parse("1,000,000",NumberStyles.Any).Dump("million");decimal.Parse("3e6",NumberStyles.Any).Dump("3million");decimal.Parse("$5.20",NumberStyles.Currency).Dump("5.2");因为我们没有指定格式提供器,因此这个例子支持本地货币符号、分组符号、小数点等。下一个例子以硬编码的方式使用欧元符号和空格分组符号来表示货币:
NumberFormatInfoni=newNumberFormatInfo();ni.CurrencySymbol="€";ni.CurrencyGroupSeparator="";double.Parse("€1000000",NumberStyles.Currency,ni).Dump("million");6.5.3DateTime格式字符串DateTime、DateTimeOffset常用的格式字符串有:
解析日期时,“月/日/年”和“日/月/年”很容易混淆。避免该问题有两种方式:
此处更推荐第二种。
这里说明:
DateTimeStyles为标记枚举,用于DateTime(Offset).Parse方法。以下是枚举成员:
Convert类可以对上述数据进行转换。
Convert类的数字转换采用银行家舍入方式,避免截断产生不符合要求的数据。
doubled=3.9;inti=Convert.ToInt32(d);//i==4如果银行舍入方式不适用,可以使用Math.Round方法,该方法可以使用额外参数控制中间值舍入方式。
银行家舍入方式,又称为“四舍六入五考虑”,是一种特殊的四舍五入规则,其核心原则如下:
这种舍入方式的优点是减少了在大量计算时舍入误差积累的偏差,使得结果更加公平和中性。银行家舍入法广泛应用于财务计算、统计学以及计算机科学中,特别是在浮点数运算和金融软件开发中,它有助于确保在四舍五入时整体上保持数值的准确性和公平性。
ToXXX方法包括一些重载方法,可以将字符串解析为其他进制:
intthirty=Convert.ToInt32("1E",16);//Parseinhexadecimaluintfive=Convert.ToUInt32("101",2);//Parseinbinary第二个参数指定的进制必须是2、8、10、16进制。
Convert.ChangeType()方法可以进行动态转换:
TypetargetType=typeof(int);objectsource="42";objectresult=Convert.ChangeType(source,targetType);其中source和targetType必须是基本类型。
上述方法的用途之一是编写可以处理多种类型的反序列化器。它还能够将任意枚举类型转换为对应的整数类型。
Base64使用ASCII字符集中的64个字符将二进制数据编码为可读的字符。
可以使用Convert.ToBase64String将字节数组转换为Base64格式,使用Convert.FromBase64String执行相反的操作。
XmlConvert的方法不需要提供特殊的格式字符串就能够处理XML格式的细微差别。
其格式化方法均为重载的ToString方法,解析方法为ToBoolean、ToDateTime等:
strings=XmlConvert.ToString(true);//s="true",而非"True"XmlConvert.ToBoolean(s).Dump();DateTime转换XmlConvert.ToString(DateTime)和XmlConvert.ToDateTime()(格式化和解析)方法可以接受XmlDateTimeSerializationMode枚举参数,其元素为:
所有类型转换器都继承自TypeConverter,获取转换器需要通过TypeDescriptor类的GetConverter方法,方式如下:
TypeConvertercc=TypeDescriptor.GetConverter(typeof(Color));TypeConverter常用的方法有ConvertToString和ConvertFromString:
Colorbeige=(Color)cc.ConvertFromString("Beige");Colorpurple=(Color)cc.ConvertFromString("#800080");Colorwindow=(Color)cc.ConvertFromString("Window");按照惯例,类型转换器的名称应以Converter结尾,并且通常与它们转换的类型位于同一个命名空间中。类型是通过TypeConverterAttribute与转换器联系在一起的。这样设计器就可以自动获得对应的转换器了。
类型转换器还可以提供一些设计时的服务,例如为设计器生成标准的下拉列表项,或者辅助代码序列化。
除decimal和DateTime(Offset)类型外,其他基本类型都可以通过BitConverter.GetBytes转换为字节数组:
foreach(bytebinBitConverter.GetBytes(3.5))Console.Write(b+"");//0000001264BitConverter还提供了将字节数组反向解析的方法,如BitConverter.ToDouble
Warn
BitConverter也不支持string
BitConverter不支持decimal。可以通过decimal.GetBits方法得到相似的结果。
decimal.GetBits方法返回int数组,且decimal也有接受int数组的构造器。
BitConverter不支持DateTime(Offset),但是可以通过DateTime.ToBinary方法得到日期对应的long,再通过BitConverter.GetBytes得到对应数组。
解析时通过BitConverter.ToLong和DateTime.FromBinary方法进行。
DateTime(longticks)和DateTime.FromBinary(longdateData)的区别
全球化专注于三个任务(重要性从大到小):
以下是一些必要任务的总结:
可以通过Thread.CurrentCulture属性来模拟不同文化:
Thread.CurrentThread.CurrentCulture=CultureInfo.GetCultureInfo("tr-TR");这里推荐模拟土耳其文化进行测试,原因如下:
可以通过修改控制面板中的数字、日期格式进行测试,这将影响默认文化设置(CultureInfo.CurrentCulture)。
Info
更多内容见18.6.4.2测试附属程序集、18.6.5文化和子文化
Math中定义的舍入方法有4个:
Eureka
Math.Truncate和Math.Floor看起来功能相同,但是面对负数他们的行为将出现大不同!
BigInteger可以表示任意大的整数而不会丢失精度。它有如下特点:
基础数值类型和BigInteger可以显式相互转换,但可能损失精度:
doubleg1=1e100;//implicitcastBigIntegerg2=(BigInteger)g1;//explicitcastg2.Dump();//输出:100000000000000001590289110975991804683608085639452813897813275577478387721703810608134699858568151046.8.4Complex(复数)Complex实例化方式如下:
varc1=newComplex(2,3.5);varc2=newComplex(3,0);Complex类型有如下特点:
Random的随机性不够高,.NETFramework提供了一种密码强度的随机数生成器RandomNumberGenerator。使用方式如下:
varrand=System.Security.Cryptography.RandomNumberGenerator.Create();byte[]bytes=newbyte[4];rand.GetBytes(bytes);//Fillthebytearraywithrandomnumbers.BitConverter.ToInt32(bytes,0).Dump("Acryptographicallystrongrandominteger");该生成器通过填充字节数组产生随机数,不够灵活,要通过BitConverter获取相应的随机数。
枚举隐式派生自System.Enum类型,因此都可以隐式转换为System.Enum实例:
enumNut{Walnut,Hazelnut,Macadamia}enumSize{Small,Medium,Large}staticvoidMain(){ Display(Nut.Macadamia);//Nut.Macadamia Display(Size.Large);//Size.Large}staticvoidDisplay(Enumvalue) //TheEnumtypeunifiesallenums{ Console.WriteLine(value.GetType().Name+"."+value.ToString());}6.9.1枚举值转换6.9.1.1将枚举转换为整数对于System.Enum实例,将枚举值转换为整数有4种方式:
staticintGetIntegralValue(EnumanyEnum) =>(int)(object)anyEnum;该方法也有缺陷,当anyEnum对应的是long类型,上述转化将抛出InvalidCastException。
staticdecimalGetAnyIntegralValue(EnumanyEnum) =>Convert.ToDecimal(anyEnum);此处用到了任何整形都可以转换为decimal的特点。
此处不能是如下代码,否则也会抛出InvalidCastException:
(decimal)(object)anyEnum;方法三:使用Convert.ChangeTypestaticobjectGetBoxedIntegralValue(EnumanyEnum){ TypeintegralType=Enum.GetUnderlyingType(anyEnum.GetType()); returnConvert.ChangeType(anyEnum,integralType);}此处先用Enum.GetUnderlyingType获取enum的整数类型,再使用Convert.ChangeType进行转化。该方法会保持原始的整数类型。
注意,需要指定格式字符串“D”或“d”,否则将得到枚举对应的字符串而非数字。
staticstringGetIntegralValueAsString(EnumanyEnum) =>anyEnum.ToString("D");//returnssomethinglike"4"这种方式在编写自定义的序列化器时很有用。
Enum.ToObject方法将整数值转换为一个给定类型的enum实例:
[Flags]publicenumBorderSides{Left=1,Right=2,Top=4,Bottom=8}staticvoidMain(){ objectbs=Enum.ToObject(typeof(BorderSides),3); Console.WriteLine(bs);//Left,Right //Thisisthedynamicequivalentofthis: BorderSidesbs2=(BorderSides)3;}6.9.1.3字符串转换将enum转换为字符串,可以调用静态的Enum.Format或实例的ToString方法。可用的格式字符串有:
Enum.Parse方法可以将一个字符串转换为enum,使用方式如下:
BorderSidesleftRight=(BorderSides)Enum.Parse(typeof(BorderSides),"Left,Right");BorderSidesleftRightCaseInsensitive=(BorderSides)Enum.Parse(typeof(BorderSides),"left,right",true);第三个可选参数选择是否执行大小写不敏感的解析,如果成员不存在则抛出ArgumentException。
Enum.GetValues方法用于获取枚举中的所有成员(包括组合成员):
foreach(EnumvalueinEnum.GetValues(typeof(BorderSides)))Console.WriteLine(value);Enum.GetNames执行相同的操作,但返回的是一个字符串数组。
Guid可以表示的值总共有\(2^{128}\)或\(3.4×10^{18}\)个。获取Guid方法有二:
Guid有如下特点:
==和!=运算符执行静态解析(编译时确定执行方法),如下两种比较绑定的方法不同,结果不同:
如下代码将输出True:
intx=5;inty=5;Console.WriteLine(x==y);如下代码将输出False:
objectx=5;objecty=5;Console.WriteLine(x==y);6.11.2.2Object.Equals虚方法Equals是在运行时根据对象的实际类型解析的。对于引用类型,Equals默认进行引用相等比较。对于结构体,Equals会调用每一个字段的Equals进行结构化比较。
其使用方式如下,如下代码将输出True:
objectx=5;objecty=5;Console.WriteLine(x.Equals(y));6.11.2.3object.Equals静态方法object.Equals静态方法执行的操作如下:
publicstaticboolAreEqual(objectobjA,objectobjB)=>objA==nullobjB==null:objA.Equals(objB);与Object.Equals虚方法不同,该静态方法接受两个参数。它常用于==和!=无法使用的场景(编译时无法确认类型的场景),譬如泛型实例比较:
classTest
object.ReferenceEquals可以进行引用比较,防止Equals方法、==、!=重载导致的引用比较失效。
在C#8.0可以通过is运算符判断是否引用相同,详见PatternMatching
详见DO:值类型需要实现IEquatable。
之前提到,有时==和Equals应用不同的相等定义是非常有用的。
常见的不等价原因有两种:
例如如下代码分别输出“False、True”:
doublex=double.NaN;Console.WriteLine(x==x);Console.WriteLine(x.Equals(x))double类型的==运算符强制规定一个NaN不等于任何对象,即使是另一个值也是NaN。这从数学角度来说是非常自然的,并且也反映了底层CPU的行为。然而,Equals方法必须支持自反相等,换句话说:
x.Equals(x)必须总是返回true。
集合和字典需要Equals保持这个行为,否则就无法找到之前存储的项目了。
Equals和==含义不同这种做法在引用类型中要多得多,开发者自定义Equals实现值的相等比较,而仍旧令==执行(默认的)引用相等比较。StringBuilder类就是采用了这种方式,如下代码将输出“False、True”:
varsb1=newStringBuilder("foo");varsb2=newStringBuilder("foo");Console.WriteLine(sb1==sb2);Console.WriteLine(sb1.Equals(sb2));6.11.3相等比较和自定义类型6.11.3.3如何重写相等语义下面是重写相等语义的步骤:
在实现GetHashCode方法时,当类型拥有多于两个的字段,由JoshBloch推荐的模式能够在结果良好的情况下同时保证性能:
inthash=17;//17=someprimenumberhash=hash*31+field1.GetHashCode();//31=anotherprimenumberhash=hash*31+field2.GetHashCode();hash=hash*31+field3.GetHashCode();...returnhash;6.12顺序比较6.12.1IComparableIComparable的定义方式如下:
publicinterfaceIComparable{intCompareTo(objectother);}publicinterfaceIComparable
换句话说,相等比较是严格的。例如:字符串""和"ǖ"用Equals比较时是不同的,然而用CompareTo比较时则是相同的。总之,CompareTo永远比不上Equals更严格。
Console.Out属性、Console.SetOut、Console.SetIn可以搭配使用,重定向Console的输入和输出流:
//保留原输出流varoldOut=Console.Out;//输出至文件using(TextWriterw=File.CreateText("D:\\output.txt")){Console.SetOut(w);Console.WriteLine("Helloworld");}//恢复原标准输出流Console.SetOut(oldOut);6.13.2Environment类该类提供了很多有用的属性:
一些方法:
System.AppContext类提供了一个全局的开关配置表,以方便消费者控制新功能的开启和关闭状态。这种隐含的方式可用于添加实验功能而大多数用户并不知道它的存在。
例如,开发者可以使用如下方式启用程序库的某个功能:
boolisDefined,switchValue;isDefined=AppContext.TryGetSwitch("MyLibrary.SomeBreakingChange",outswitchValue);TryGetSwitch方法将在开关未定义的情况下返回false。这样我们就可以区分未定义和值为false这两种不同的情况了,这种区分是非常必要的。