編程時所需的許多核心功能不是由 C# 語言提供的,而是由 .NET BCL 中的類型提供的。在本章中,我們將介紹有助于完成基本編程任務(wù)的類型,例如虛擬相等比較、順序比較和類型轉(zhuǎn)換。我們還介紹了基本的 .NET 類型,例如字符串、日期時間和枚舉。
本節(jié)中的類型駐留在 System 命名空間中,但以下:
C# 字符表示單個 Unicode 字符,并為 System.Char 結(jié)構(gòu)設(shè)置別名。在第 中,我們描述了如何表達字符文字:
char c='A';
char newLine='\n';
System.Char 定義了一系列用于處理字符的靜態(tài)方法,ToLower 和 IsWhiteSpace 。您可以通過 類型或其 char 別名調(diào)用這些:
Console.WriteLine (System.Char.ToUpper ('c')); // C
Console.WriteLine (char.IsWhiteSpace ('\t')); // True
ToUpper 和 ToLower 遵循最終用戶的區(qū)域設(shè)置,這可能會導致細微的錯誤。以下表達式在土耳其的計算結(jié)果為 false:
char.ToUpper ('i')=='I'
原因是因為在土耳其,炭。ToUpper ('i') 是 '?'(注意上面的點!)。為了避免這個問題,System.Char(和System.String)還提供了ToUpper和ToLower的區(qū)域性不變版本,以單詞結(jié)尾。這些始終適用英語區(qū)域性規(guī)則:
Console.WriteLine (char.ToUpperInvariant ('i')); // I
這是以下各項的快捷方式:
Console.WriteLine (char.ToUpper ('i', CultureInfo.InvariantCulture))
有關(guān)區(qū)域設(shè)置和區(qū)域性的更多信息,請參閱
char 剩下的大多數(shù)靜態(tài)方法都與字符分類有關(guān)。 列出了這些內(nèi)容。
用于對字符進行分類的靜態(tài)方法 | ||
靜態(tài)方法 | 包含的字符 | 包括 Unicode 類別 |
是信 | A–Z、a–z 和其他字母的字母 | 大寫字母 小寫字母標題大小寫字母修飾符字母其他字母 |
IsUpper | 大寫字母 | 大寫字母 |
伊斯洛爾 | 小寫字母 | 小寫字母 |
IsDigit | 0–9 加其他字母的數(shù)字 | 十進制數(shù)字數(shù) |
IsLetterOrDigit | 字母加數(shù)字 | ( IsLetter , IsDigit ) |
編號 | 所有數(shù)字加上 Unicode 分數(shù)和羅馬數(shù)字符號 | 十進制數(shù)字數(shù)字字母數(shù)字其他數(shù)字 |
分隔符 | 空格加上所有 Unicode 分隔符 | 行分隔符段落分隔符 |
是空白空間 | 所有分隔符加上 \n 、\r 、\t 、\f 和 \v | 行分隔符段落分隔符 |
標點符號 | 西方和其他字母表中用于標點符號的符號 | 破折號標點符號連接器標點符號首字母報價標點符號最終報價標點符號 |
是符號 | 大多數(shù)其他可打印符號 | 數(shù)學符號修飾符符號其他符號 |
是控制 | 0x20下方不可打印的“控制”字符,如 \r 、\n 、\t 、>0x20下方不可打印的“控制”字符,如 \r 、\n 、\t 、\0 以及介于 0x7F 和 0x9A 之間的字符< 以及介于 0x7F 和 0x9A 之間的字符 | (無) |
對于更精細的分類,char 提供了一個名為 GetUnicodeCategory 的靜態(tài)方法;這將返回一個 UnicodeCategory 枚舉,其成員顯示在 的最右側(cè)列中。
通過從整數(shù)顯式轉(zhuǎn)換,可以在分配的 Unicode 集之外生成字符。要測試字符的有效性,請調(diào)用 char。GetUnicodeCategory:如果結(jié)果是 UnicodeCategory.OtherNotAssigned ,則該字符無效。
字符的寬度為 16 位,足以表示中的任何 Unicode 字符。除此之外,您必須使用代理項對:我們在中描述了執(zhí)行此操作的方法。
C# 字符串 (==System.String ) 是不可變(不可更改)的字符序列。在第 中,我們描述了如何表達字符串文字、執(zhí)行相等比較以及連接兩個字符串。本節(jié)介紹用于處理字符串的其余函數(shù),這些函數(shù)通過 System.String 類的靜態(tài)和實例成員公開。
構(gòu)造字符串的最簡單方法是分配文本,如所示:
string s1="Hello";
string s2="First Line\r\nSecond Line";
string s3=@"\\server\fileshare\helloworld.cs";
要創(chuàng)建重復(fù)的字符序列,可以使用字符串的構(gòu)造函數(shù):
Console.Write (new string ('*', 10)); // **********
您還可以從 char 數(shù)組構(gòu)造字符串。ToCharArray 方法執(zhí)行相反的操作:
char[] ca="Hello".ToCharArray();
string s=new string (ca); // s="Hello"
字符串的構(gòu)造函數(shù)也被重載以接受各種(不安全的)指針類型,以便從 char* 等類型創(chuàng)建字符串。
空字符串的長度為零。若要創(chuàng)建空字符串,可以使用文本字符串或靜態(tài)字符串。空字段;若要測試空字符串,可以執(zhí)行相等比較或測試其 Length 屬性:
string empty="";
Console.WriteLine (empty==""); // True
Console.WriteLine (empty==string.Empty); // True
Console.WriteLine (empty.Length==0); // True
由于字符串是引用類型,因此它們也可以為 null :
string nullString=null;
Console.WriteLine (nullString==null); // True
Console.WriteLine (nullString==""); // False
Console.WriteLine (nullString.Length==0); // NullReferenceException
靜態(tài)字符串。方法是一個有用的快捷方式,用于測試給定字符串是空還是空。
字符串的索引器在給定索引處返回單個字符。與所有對字符串進行操作的函數(shù)一樣,這是零索引:
string str="abcde";
char letter=str[1]; // letter=='b'
字符串還實現(xiàn)了 IEnumerable<char> ,因此您可以對其進行 foreach :
foreach (char c in "123") Console.Write (c + ","); // 1,2,3,
在字符串中搜索的最簡單方法是 開始與 、結(jié)束與 和包含 。這些都返回真或假:
Console.WriteLine ("quick brown fox".EndsWith ("fox")); // True
Console.WriteLine ("quick brown fox".Contains ("brown")); // True
StartsWith 和 EndsWith 已重載,以便指定 StringComparison 枚舉或 CultureInfo 對象來控制區(qū)分大小寫和區(qū)域性(請參閱)。默認設(shè)置是使用適用于當前(本地化)區(qū)域性的規(guī)則執(zhí)行區(qū)分大小寫的匹配。以下操作使用區(qū)域性的規(guī)則執(zhí)行不區(qū)分大小寫的搜索:
"abcdef".StartsWith ("aBc", StringComparison.InvariantCultureIgnoreCase)
包含方法不提供此重載的便利性,盡管您可以使用 IndexOf 方法獲得相同的結(jié)果。
IndexOf 功能更強大:它返回給定字符或子字符串的第一個位置(如果未找到子字符串,則返回 ?1):
Console.WriteLine ("abcde".IndexOf ("cd")); // 2
IndexOf 也被重載以接受 startPosition(從中開始搜索的索引)以及 StringComparison 枚舉:
Console.WriteLine ("abcde abcde".IndexOf ("CD", 6,
StringComparison.CurrentCultureIgnoreCase)); // 8
LastIndexOf 類似于 IndexOf ,但它通過字符串向后工作。
IndexOfAny 返回一組字符中任何一個字符的第一個匹配位置:
Console.Write ("ab,cd ef".IndexOfAny (new char[] {' ', ','} )); // 2
Console.Write ("pas5w0rd".IndexOfAny ("0123456789".ToCharArray() )); // 3
LastIndexOfAny 在相反的方向上做同樣的事情。
因為 String 是不可變的,所以所有“操作”字符串的方法都會返回一個新字符串,而原始方法保持不變(重新分配字符串變量時也是如此)。
子字符串提取字符串的一部分:
string left3="12345".Substring (0, 3); // left3="123";
string mid3="12345".Substring (1, 3); // mid3="234";
如果省略長度,則會得到字符串的其余部分:
string end3="12345".Substring (2); // end3="345";
插入和刪除 在指定位置插入或刪除字符:
string s1="helloworld".Insert (5, ", "); // s1="hello, world"
string s2=s1.Remove (5, 2); // s2="helloworld";
PadLeft 和 PadRight 使用指定的字符(如果未指定,則為空格)將字符串填充到給定長度:
Console.WriteLine ("12345".PadLeft (9, '*')); // ****12345
Console.WriteLine ("12345".PadLeft (9)); // 12345
如果輸入字符串的長度大于填充長度,則返回的原始字符串將保持不變。
修剪開始 和 修剪結(jié)束 從字符串的開頭或結(jié)尾刪除指定的字符;修剪兩者兼而有之。默認情況下,這些函數(shù)刪除空格字符(包括空格、制表符、換行符和這些字符的 Unicode 變體):
Console.WriteLine (" abc \t\r\n ".Trim().Length); // 3
替換替換特定字符或的所有(非重疊)匹配項:
Console.WriteLine ("to be done".Replace (" ", " | ") ); // to | be | done
Console.WriteLine ("to be done".Replace (" ", "") ); // tobedone
ToUpper 和 ToLower 返回輸入字符串的大寫和小寫版本。默認情況下,它們遵循用戶的當前語言設(shè)置;ToUpperInvariant 和 ToLowerInvariant 始終應(yīng)用英文字母規(guī)則。
拆分將字符串分成幾部分:
string[] words="The quick brown fox".Split();
foreach (string word in words)
Console.Write (word + "|"); // The|quick|brown|fox|
默認情況下,拆分使用空格字符作為分隔符;它也被重載以接受字符或字符串分隔符的參數(shù)數(shù)組。 Split 還可以選擇接受 StringSplitOptions 枚舉,該枚舉具有刪除空條目的選項:當單詞由一行中的多個分隔符分隔時,這很有用。
靜態(tài)連接方法執(zhí)行與拆分相反的操作。它需要一個分隔符和字符串數(shù)組:
string[] words="The quick brown fox".Split();
string together=string.Join (" ", words); // The quick brown fox
靜態(tài) Concat 方法類似于 Join,但只接受參數(shù)字符串數(shù)組,并且不應(yīng)用分隔符。Concat 完全等價于 + 運算符(編譯器實際上將 + 轉(zhuǎn)換為 Concat):
string sentence=string.Concat ("The", " quick", " brown", " fox");
string sameSentence="The" + " quick" + " brown" + " fox";
靜態(tài) Format 方法提供了一種生成嵌入變量的字符串的便捷方法。嵌入的變量(或值)可以是任何類型的;格式只是在它們上調(diào)用 ToString。
包含嵌入變量的主字符串稱為。調(diào)用字符串時。格式 ,您需要提供一個復(fù)合格式字符串,后跟每個嵌入變量:
string composite="It's {0} degrees in {1} on this {2} morning";
string s=string.Format (composite, 35, "Perth", DateTime.Now.DayOfWeek);
// s=="It's 35 degrees in Perth on this Friday morning"
(那是攝氏!
我們可以使用內(nèi)插字符串文字來實現(xiàn)相同的效果(請參閱)。只需在字符串前面加上 $ 符號,并將表達式放在大括號中:
string s=$"It's hot this {DateTime.Now.DayOfWeek} morning";
大括號中的每個數(shù)字稱為。該數(shù)字對應(yīng)于參數(shù)位置,可以選擇后跟:
最小寬度對于對齊列很有用。如果值為負數(shù),則數(shù)據(jù)左對齊;否則,它是右對齊的:
string composite="Name={0,-20} Credit Limit={1,15:C}";
Console.WriteLine (string.Format (composite, "Mary", 500));
Console.WriteLine (string.Format (composite, "Elizabeth", 20000));
結(jié)果如下:
Name=Mary Credit Limit=$500.00
Name=Elizabeth Credit Limit=$20,000.00
這是不使用字符串的等效項。格式:
string s="Name=" + "Mary".PadRight (20) +
" Credit Limit=" + 500.ToString ("C").PadLeft (15);
信用額度通過“C”格式字符串格式化為貨幣。我們在中詳細描述了格式字符串。
在比較兩個值時,.NET 區(qū)分比較和的概念。相等比較測試兩個實例在語義上是否相同;順序比較測試在按升序或降序排列兩個(如果有)實例時,哪個實例排在第一位。
相等比較不是順序比較的;這兩個系統(tǒng)有不同的目的。例如,在同一排序位置有兩個不相等的值是合法的。我們在中繼續(xù)這個話題。
對于字符串相等性比較,可以使用==運算符或字符串的 Equals 方法之一。后者更通用,因為它們允許您指定不區(qū)分大小寫等選項。
另一個區(qū)別是,如果變量被強制轉(zhuǎn)換為對象類型,==不能可靠地處理字符串。我們在中解釋了為什么會這樣。
對于字符串順序比較,可以使用 CompareTo 實例方法或靜態(tài) Compare 和 CompareOrdinal 方法。它們返回一個正數(shù)或負數(shù),或者零,具體取決于第一個值是在第二個值之后、之前還是旁邊。
在詳細介紹每個細節(jié)之前,我們需要檢查 .NET 的底層字符串比較算法。
字符串比較有兩種基本算法:和。序號比較將字符簡單地解釋為數(shù)字(根據(jù)其數(shù)字 Unicode 值);區(qū)分區(qū)域性的比較根據(jù)特定字母表解釋字符。有兩種特殊的區(qū)域性:“當前區(qū)域性”,它基于從計算機的控制面板中選取的設(shè)置,以及“固定區(qū)域性”,它在每臺計算機上都是相同的(并且與美國區(qū)域性非常匹配)。
對于相等比較,序數(shù)和特定于區(qū)域性的算法都很有用。但是,對于排序,特定于區(qū)域性的比較幾乎總是更可取的:要按字母順序?qū)ψ址M行排序,您需要一個字母表。序號依賴于數(shù)字 Unicode 點值,這些值恰好將英語字符按字母順序排列,但即便如此,也不完全符合您的預(yù)期。例如,假設(shè)區(qū)分大小寫,請考慮字符串 “Atom” , “atom” 和 “Zamia” 。固定區(qū)域性將它們按以下順序排列:
"atom", "Atom", "Zamia"
序號按如下方式排列它們:
"Atom", "Zamia", "atom"
這是因為固定區(qū)域性封裝了一個字母表,該字母表將大寫字符與其小寫對應(yīng)字符相鄰(aAbBcCdD...)。但是,序號算法首先放置所有大寫字符,然后放置所有小寫字符(A...Z,一個...這本質(zhì)上是對 1960 年代發(fā)明的 ASCII 字符集的回歸。
盡管序號有限制,但字符串的==運算符始終執(zhí)行的比較。字符串的實例版本也是如此。在沒有參數(shù)的情況下調(diào)用時等于;這定義了字符串類型的“默認”相等比較行為。
序數(shù)算法被選為字符串的==和 Equals 函數(shù),因為它既高效又。字符串相等比較被認為是基本的,并且比順序比較更頻繁地執(zhí)行。
相等的“嚴格”概念也與==運算符的一般用法一致。
以下方法允許區(qū)分區(qū)域性或不區(qū)分大小寫的比較:
public bool Equals(string value, StringComparison comparisonType);
public static bool Equals (string a, string b,
StringComparison comparisonType);
靜態(tài)版本的優(yōu)點在于,如果一個或兩個字符串為 null,它仍然有效。 StringComparison 是一個枚舉,定義如下:
public enum StringComparison
{
CurrentCulture, // Case-sensitive
CurrentCultureIgnoreCase,
InvariantCulture, // Case-sensitive
InvariantCultureIgnoreCase,
Ordinal, // Case-sensitive
OrdinalIgnoreCase
}
例如:
Console.WriteLine (string.Equals ("foo", "FOO",
StringComparison.OrdinalIgnoreCase)); // True
Console.WriteLine ("?"=="ǖ"); // False
Console.WriteLine (string.Equals ("?", "ǖ",
StringComparison.CurrentCulture)); // ?
(第三個示例的結(jié)果由計算機的當前語言設(shè)置確定。
字符串的 CompareTo 實例方法執(zhí)行、順序比較。與==運算符不同,CompareTo 不使用序號比較:對于排序,區(qū)分區(qū)域性的算法要有用得多。下面是該方法的定義:
public int CompareTo (string strB);
CompareTo 實例方法實現(xiàn)了通用 IComparable 接口,這是跨 .NET 庫使用的標準比較協(xié)議。這意味著字符串的 CompareTo 定義了字符串在諸如排序集合之類的應(yīng)用程序中的默認排序行為。有關(guān) IComparable 的更多信息,請參閱
對于其他類型的比較,可以調(diào)用靜態(tài) 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);
最后兩個方法只是調(diào)用前兩個方法的快捷方式。
所有順序比較方法都返回正數(shù)、負數(shù)或零,具體取決于第一個值是在第二個值之后、之前還是旁邊:
Console.WriteLine ("Boston".CompareTo ("Austin")); // 1
Console.WriteLine ("Boston".CompareTo ("Boston")); // 0
Console.WriteLine ("Boston".CompareTo ("Chicago")); // -1
Console.WriteLine ("?".CompareTo ("ǖ")); // 0
Console.WriteLine ("foo".CompareTo ("FOO")); // -1
下面使用當前區(qū)域性執(zhí)行不區(qū)分大小寫的比較:
Console.WriteLine (string.Compare ("foo", "FOO", true)); // 0
通過提供 CultureInfo 對象,您可以插入任何字母表:
// CultureInfo is defined in the System.Globalization namespace
CultureInfo german=CultureInfo.GetCultureInfo ("de-DE");
int i=string.Compare ("Müller", "Muller", false, german);
類(System.Text 命名空間)表示可變(可編輯)字符串。使用字符串生成器,您可以追加、插入、刪除和替換子字符串,而無需替換整個字符串生成器。
StringBuilder 的構(gòu)造函數(shù)可以選擇接受初始字符串值以及其內(nèi)部容量的起始大小(默認為 16 個字符)。如果超出此范圍,StringBuilder 會自動調(diào)整其內(nèi)部結(jié)構(gòu)的大小,以容納(以輕微的性能成本)達到其最大容量(默認值為 int。最大值)。
StringBuilder 的一個流行用途是通過重復(fù)調(diào)用 Append 來構(gòu)建一個長字符串。此方法比重復(fù)連接普通字符串類型要高效得多:
StringBuilder sb=new StringBuilder();
for (int i=0; i < 50; i++) sb.Append(i).Append(",");
要獲得最終結(jié)果,請調(diào)用 ToString():
Console.WriteLine (sb.ToString());
0,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,
追加行執(zhí)行添加新行序列的追加(在 Windows 中為“\r\n”)。AppendFormat 接受復(fù)合格式字符串,就像 String.Format 一樣。
除了 Insert 、Remove 和 replace 方法(Replace 的工作方式與字符串的 Replace 類似)之外,StringBuilder 還定義了一個 Length 屬性和一個可寫索引器,用于獲取/設(shè)置單個字符。
要清除字符串生成器的內(nèi)容,請實例化一個新字符串或?qū)⑵溟L度設(shè)置為零。
將 StringBuilder 的長度設(shè)置為零不會縮小其容量。因此,如果 StringBuilder 之前包含 100 萬個字符,則在將其長度歸零后,它將繼續(xù)占用大約 2 兆字節(jié)的內(nèi)存。如果要釋放內(nèi)存,則必須創(chuàng)建一個新的 StringBuilder 并允許舊的 StringBuilder 退出范圍(并被垃圾回收)。
字符集是字符的分配,每個字符都有一個數(shù)字代碼或碼位。有兩種常用的字符集:Unicode和ASCII。Unicode具有大約一百萬個字符的地址空間,其中大約100,000個當前已分配。Unicode涵蓋了世界上大多數(shù)的語言以及一些歷史語言和特殊符號。ASCII集只是Unicode集的前128個字符,它涵蓋了您在美式鍵盤上看到的大部分內(nèi)容。ASCII比Unicode早了30年,有時仍因其簡單和高效而被使用:每個字符由一個字節(jié)表示。
.NET 類型系統(tǒng)旨在使用 Unicode 字符集。但是,ASCII 是隱式支持的,因為它是 Unicode 的一個子集。
將字符從其數(shù)字代碼點映射到二進制表示形式。在 .NET 中,文本編碼主要在處理文本文件或流時發(fā)揮作用。將文本文件讀入字符串時,會將文件數(shù)據(jù)從二進制轉(zhuǎn)換為字符和字符串類型所需的內(nèi)部 Unicode 表示形式。文本編碼可以限制可以表示的字符以及影響存儲效率。
.NET 中有兩種類型的文本編碼:
第一類包含遺留編碼,例如 IBM 的 EBCDIC 和 8 位字符集,這些字符集在 Unicode 之前很流行(由代碼頁標識),這些字符集在 Unicode 之前很流行。ASCII 編碼也屬于這一類:它對前 128 個字符進行編碼并刪除其他所有字符。此類別還包含GB128,這是自 18030 年以來在中國編寫或銷往中國的應(yīng)用程序的強制性標準。
第二類是UTF-8,UTF-16和UTF-32(以及過時的UTF-7)。每一個都有不同的空間效率。UTF-8對于大多數(shù)類型的文本來說是最節(jié)省空間的:它使用1到4個字節(jié)來表示每個字符。前128個字符只需要一個字節(jié),使其與ASCII兼容。UTF-8是文本文件和流(特別是在互聯(lián)網(wǎng)上)最流行的編碼,它是.NET中流輸入/輸出(I/O)的默認值(事實上,它幾乎是所有隱式使用編碼的默認值)。
UTF-16 使用一個或兩個 16 位字來表示每個字符。這就是 .NET 在內(nèi)部用來表示字符和字符串的內(nèi)容。某些程序還以 UTF-16 寫入文件。
UTF-32 最節(jié)省空間:它將每個代碼點直接映射到 32 位,因此每個字符消耗四個字節(jié)。出于這個原因,很少使用 UTF-32。但是,它確實使隨機訪問變得非常容易,因為每個字符占用的字節(jié)數(shù)相等。
System.Text 中的編碼類是封裝文本編碼的類的通用基類型。有幾個子類 — 它們的目的是封裝具有相似特征的編碼族。最常見的編碼可以通過編碼上的專用靜態(tài)屬性獲得:
編碼名稱 | 編碼上的靜態(tài)屬性 |
UTF-8 | Encoding.UTF8 |
UTF-16 | Encoding.Unicode(UTF16) |
UTF-32 | Encoding.UTF32 |
ASCII | Encoding.ASCII |
您可以通過使用標準互聯(lián)網(wǎng)號碼分配機構(gòu) (IANA) 字符集名稱調(diào)用 Encoding.GetEncoding 來獲取其他編碼:
// In .NET 5+ and .NET Core, you must first call RegisterProvider:
Encoding.RegisterProvider (CodePagesEncodingProvider.Instance);
Encoding chinese=Encoding.GetEncoding ("GB18030");
靜態(tài) GetEncodings 方法返回所有受支持編碼的列表及其標準 IANA 名稱:
foreach (EncodingInfo info in Encoding.GetEncodings())
Console.WriteLine (info.Name);
獲取編碼的另一種方法是直接實例化編碼類。這樣做允許您通過構(gòu)造函數(shù)參數(shù)設(shè)置各種選項,包括:
編碼對象最常見的應(yīng)用程序是控制文本的讀取和寫入文件或流的方式。例如,下面寫“正在測試...”到名為 的 UTF-16 編碼文件:
System.IO.File.WriteAllText ("data.txt", "Testing...", Encoding.Unicode);
如果省略最后一個參數(shù),WriteAllText 將應(yīng)用無處不在的 UTF-8 。
UTF-8 是所有文件和流 I/O 的默認文本編碼。
我們將在第 中繼續(xù)討論此主題。
還可以使用編碼對象來回字節(jié)數(shù)組。GetBytes 方法使用給定的編碼從字符串轉(zhuǎn)換為 byte[]; GetString 從 byte[] 轉(zhuǎn)換為字符串:
byte[] utf8Bytes=System.Text.Encoding.UTF8.GetBytes ("0123456789");
byte[] utf16Bytes=System.Text.Encoding.Unicode.GetBytes ("0123456789");
byte[] utf32Bytes=System.Text.Encoding.UTF32.GetBytes ("0123456789");
Console.WriteLine (utf8Bytes.Length); // 10
Console.WriteLine (utf16Bytes.Length); // 20
Console.WriteLine (utf32Bytes.Length); // 40
string original1=System.Text.Encoding.UTF8.GetString (utf8Bytes);
string original2=System.Text.Encoding.Unicode.GetString (utf16Bytes);
string original3=System.Text.Encoding.UTF32.GetString (utf32Bytes);
Console.WriteLine (original1); // 0123456789
Console.WriteLine (original2); // 0123456789
Console.WriteLine (original3); // 0123456789
回想一下,.NET 以 UTF-16 格式存儲字符和字符串。由于 UTF-16 要求每個字符一個或兩個 16 位字,而一個字符的長度僅為 16 位,因此某些 Unicode 字符需要兩個字符來表示。這有幾個:
大多數(shù)應(yīng)用程序都忽略了這一點,因為幾乎所有常用字符都適合稱為 (BMP) 的 Unicode 部分,該部分只需要一個 UTF-16 的 16 位字。BMP涵蓋了幾十種世界語言,包括30,000多個漢字。不包括一些古代語言的字符,樂譜符號和一些不太常見的漢字。
如果需要支持兩個單詞字符,char 中的以下靜態(tài)方法會將 32 位代碼點轉(zhuǎn)換為包含兩個字符的字符串,然后再轉(zhuǎn)換回來:
string ConvertFromUtf32 (int utf32)
int ConvertToUtf32 (char highSurrogate, char lowSurrogate)
兩個單詞的字符稱為項。它們很容易發(fā)現(xiàn),因為每個單詞都在0xD800到0xDFFF的范圍內(nèi)。您可以在 char 中使用以下靜態(tài)方法來提供幫助:
bool IsSurrogate (char c)
bool IsHighSurrogate (char c)
bool IsLowSurrogate (char c)
bool IsSurrogatePair (char highSurrogate, char lowSurrogate)
命名空間中的 StringInfo 類還提供了一系列用于處理雙字字符的方法和屬性。
BMP 之外的字符通常需要特殊字體,并且操作系統(tǒng)支持有限。
System 命名空間中的以下不可變結(jié)構(gòu)執(zhí)行表示日期和時間的工作:DateTime 、DateTimeOffset 、TimeSpan 、DataOnly 和 TimeOnly 。C# 沒有定義映射到這些類型的任何特殊關(guān)鍵字。
時間跨度表示時間間隔或一天中的某個時間。在后一個角色中,它只是“時鐘”時間(沒有日期),相當于午夜以來的時間,假設(shè)沒有夏令時轉(zhuǎn)換。TimeSpan的分辨率為100 ns,最大值約為10萬天,可以是正數(shù)或數(shù)。
有三種方法可以構(gòu)造時間跨度:
以下是構(gòu)造函數(shù):
public TimeSpan (int hours, int minutes, int seconds);
public TimeSpan (int days, int hours, int minutes, int seconds);
public TimeSpan (int days, int hours, int minutes, int seconds,
int milliseconds);
public TimeSpan (long ticks); // Each tick=100ns
靜態(tài)從...當您只想以單個單位(如分鐘、小時等)指定間隔時,方法更方便:
public static TimeSpan FromDays (double value);
public static TimeSpan FromHours (double value);
public static TimeSpan FromMinutes (double value);
public static TimeSpan FromSeconds (double value);
public static TimeSpan FromMilliseconds (double value);
例如:
Console.WriteLine (new TimeSpan (2, 30, 0)); // 02:30:00
Console.WriteLine (TimeSpan.FromHours (2.5)); // 02:30:00
Console.WriteLine (TimeSpan.FromHours (-2.5)); // -02:30:00
TimeSpan 重載<和>運算符以及 + 和 - 運算符。以下表達式的計算結(jié)果為 2.5 小時的時間跨度:
TimeSpan.FromHours(2) + TimeSpan.FromMinutes(30);
下一個表達式的計算結(jié)果是少 10 天一秒:
TimeSpan.FromDays(10) - TimeSpan.FromSeconds(1); // 9.23:59:59
使用這個表達式,我們可以說明整數(shù)屬性天、小時、分鐘、秒和毫秒:
TimeSpan nearlyTenDays=TimeSpan.FromDays(10) - TimeSpan.FromSeconds(1);
Console.WriteLine (nearlyTenDays.Days); // 9
Console.WriteLine (nearlyTenDays.Hours); // 23
Console.WriteLine (nearlyTenDays.Minutes); // 59
Console.WriteLine (nearlyTenDays.Seconds); // 59
Console.WriteLine (nearlyTenDays.Milliseconds); // 0
相比之下,總...屬性返回描述整個時間跨度的雙精度類型的值:
Console.WriteLine (nearlyTenDays.TotalDays); // 9.99998842592593
Console.WriteLine (nearlyTenDays.TotalHours); // 239.999722222222
Console.WriteLine (nearlyTenDays.TotalMinutes); // 14399.9833333333
Console.WriteLine (nearlyTenDays.TotalSeconds); // 863999
Console.WriteLine (nearlyTenDays.TotalMilliseconds); // 863999000
靜態(tài) Parse 方法與 ToString 相反,將字符串轉(zhuǎn)換為 TimeSpan。 TryParse 執(zhí)行相同的操作,但如果轉(zhuǎn)換失敗,則返回 false 而不是引發(fā)異常。類還提供了遵循標準 XML 格式協(xié)議的 TimeSpan /string 轉(zhuǎn)換方法。
TimeSpan 的默認值是 TimeSpan.Zero 。
TimeSpan也可以用來表示一天中的時間(自午夜以來經(jīng)過的時間)。若要獲取一天中的當前時間,請調(diào)用 DateTime.Now.TimeOfDay 。
DateTime 和 DateTimeOffset 是不可變的結(jié)構(gòu),用于表示日期和時間(可選)。它們的分辨率為 100 ns,范圍涵蓋 0001 至 9999 年。
DateTimeOffset 在功能上類似于 DateTime。它的顯著特點是它還存儲協(xié)調(diào)世界時 (UTC) 偏移量;這可以在比較不同時區(qū)的值時獲得更有意義的結(jié)果。
關(guān)于引入DateTimeOffset背后的基本原理的優(yōu)秀文章可以,標題為“DateTime的簡史”,作者是Anthony Moore。
日期時間和日期時間偏移量在處理時區(qū)的方式上有所不同。DateTime 包含一個三態(tài)標志,指示 DateTime 是否相對于:
DateTimeOffset 更具體 — 它將 UTC 的偏移量存儲為 TimeSpan:
July 01 2019 03:00:00 -06:00
這會影響相等比較,這是在日期時間和日期時間偏移之間進行選擇的主要因素。具體說來:
夏令時可以使這種區(qū)別變得重要,即使應(yīng)用程序不需要處理多個地理時區(qū)也是如此。
因此,DateTime 認為以下兩個值不同,而 DateTimeOffset 認為它們相等:
July 01 2019 09:00:00 +00:00 (GMT)
July 01 2019 03:00:00 -06:00 (local time, Central America)
在大多數(shù)情況下,DateTimeOffset 的相等邏輯更可取。例如,在計算兩個國際事件中哪一個是最近的事件時,DateTimeOffset 隱式給出正確答案。同樣,策劃分布式拒絕服務(wù)攻擊的黑客會達到日期時間偏移量!若要對 DateTime 執(zhí)行相同的操作,需要在整個應(yīng)用程序中對單個時區(qū)(通常為 UTC)進行標準化。這是有問題的,原因有兩個:
但是,DateTime 最好在運行時指定相對于本地計算機的值,例如,如果要將每個國際辦事處的存檔安排在下周日當?shù)貢r間凌晨 3 點(此時活動量最少)。在這里,DateTime 會更合適,因為它會尊重每個站點的本地時間。
在內(nèi)部,DateTimeOffset 使用短整數(shù)來存儲 UTC 偏移量(以分鐘為單位)。它不存儲任何區(qū)域信息,因此沒有任何內(nèi)容可以指示 +08:00 的偏移量是指新加坡時間還是珀斯時間。
我們在中更深入地重新審視了時區(qū)和相等比較。
SQL Server 2008 通過同名的新數(shù)據(jù)類型引入了對 DateTimeOffset 的直接支持。
DateTime 定義接受年、月和日的整數(shù)(以及可選的小時、分鐘、秒和毫秒)的構(gòu)造函數(shù):
public DateTime (int year, int month, int day);
public DateTime (int year, int month, int day,
int hour, int minute, int second, int millisecond);
如果僅指定日期,則時間將隱式設(shè)置為午夜 (0:00)。
DateTime 構(gòu)造函數(shù)還允許您指定 DateTimeKind — 具有以下值的枚舉:
Unspecified, Local, Utc
這對應(yīng)于上一節(jié)中描述的三態(tài)標志。未指定是默認值,這意味著 DateTime 與時區(qū)無關(guān)。本地表示相對于當前計算機上的本地時區(qū)。本地 DateTime 不包含有關(guān)它所引用的信息,也不包含與 UTC 的數(shù)字偏移量,與 DateTimeOffset 不同。
DateTime 的 Kind 屬性返回其 DateTimeKind 。
DateTime 的構(gòu)造函數(shù)也被重載以接受 Calendar 對象。這允許您使用 System.Globalization 中定義的任何日歷子類指定日期:
DateTime d=new DateTime (5767, 1, 1,
new System.Globalization.HebrewCalendar());
Console.WriteLine (d); // 12/12/2006 12:00:00 AM
(此示例中日期的格式取決于計算機的控制面板設(shè)置。DateTime 始終使用默認公歷 — 此示例在構(gòu)造期間進行一次性轉(zhuǎn)換。若要使用另一個日歷執(zhí)行計算,必須使用 Calendar 子類本身上的方法。
您還可以使用長 的單個刻度值構(gòu)造一個日期時間,其中是從午夜 100/01/01 開始的 0001 ns 間隔數(shù)。
為了便于操作,DateTime 提供了靜態(tài)的 FromFileTime 和 FromFileTimeUtc 方法,用于從 Windows 文件時間(指定為長)進行轉(zhuǎn)換,以及用于從 OLE 自動化日期/時間(指定為)轉(zhuǎn)換的 FromOADate 方法。
若要從字符串構(gòu)造 DateTime,請調(diào)用靜態(tài) Parse 或 ParseExact 方法。這兩種方法都接受可選標志和格式提供程序;ParseExact 也接受格式字符串。我們將在中更詳細地討論解析。
DateTimeOffset 有一組類似的構(gòu)造函數(shù)。不同之處在于,您還將 UTC 偏移量指定為 TimeSpan:
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);
時間跨度必須為整數(shù)分鐘數(shù);否則將引發(fā)異常。
DateTimeOffset 還具有接受 Calendar 對象、長值以及接受字符串的靜態(tài) Parse 和 ParseExact 方法的構(gòu)造函數(shù)。
可以使用這些構(gòu)造函數(shù)從現(xiàn)有日期時間構(gòu)造日期時間偏移量
public DateTimeOffset (DateTime dateTime);
public DateTimeOffset (DateTime dateTime, TimeSpan offset);
或隱式強制轉(zhuǎn)換:
DateTimeOffset dt=new DateTime (2000, 2, 3);
從 DateTime 到 DateTimeOffset 的隱式強制轉(zhuǎn)換非常方便,因為大多數(shù) .NET BCL 都支持 DateTime — 而不是 DateTimeOffset 。
如果未指定偏移量,則使用以下規(guī)則從 DateTime 值推斷出偏移量:
為了在另一個方向上轉(zhuǎn)換,DateTimeOffset 提供了三個返回 DateTime 類型的值的屬性:
DateTime 和 DateTimeOffset 都有一個靜態(tài) Now 屬性,該屬性返回當前日期和時間:
Console.WriteLine (DateTime.Now); // 11/11/2019 1:23:45 PM
Console.WriteLine (DateTimeOffset.Now); // 11/11/2019 1:23:45 PM -06:00
DateTime 還提供了一個 Today 屬性,該屬性僅返回日期部分:
Console.WriteLine (DateTime.Today); // 11/11/2019 12:00:00 AM
靜態(tài) UtcNow 屬性以 UTC 格式返回當前日期和時間:
Console.WriteLine (DateTime.UtcNow); // 11/11/2019 7:23:45 AM
Console.WriteLine (DateTimeOffset.UtcNow); // 11/11/2019 7:23:45 AM +00:00
所有這些方法的精度取決于操作系統(tǒng),通常在 10 到 20 毫秒的范圍內(nèi)。
DateTime 和 DateTimeOffset 提供了一組類似的實例屬性,這些屬性返回各種日期/時間元素:
DateTime dt=new DateTime (2000, 2, 3,
10, 20, 30);
Console.WriteLine (dt.Year); // 2000
Console.WriteLine (dt.Month); // 2
Console.WriteLine (dt.Day); // 3
Console.WriteLine (dt.DayOfWeek); // Thursday
Console.WriteLine (dt.DayOfYear); // 34
Console.WriteLine (dt.Hour); // 10
Console.WriteLine (dt.Minute); // 20
Console.WriteLine (dt.Second); // 30
Console.WriteLine (dt.Millisecond); // 0
Console.WriteLine (dt.Ticks); // 630851700300000000
Console.WriteLine (dt.TimeOfDay); // 10:20:30 (returns a TimeSpan)
DateTimeOffset 還具有類型為 TimeSpan 的 Offset 屬性。
這兩種類型都提供以下實例方法來執(zhí)行計算(大多數(shù)接受 double 或 int 類型的參數(shù)):
AddYears AddMonths AddDays
AddHours AddMinutes AddSeconds AddMilliseconds AddTicks
這些都返回一個新的日期時間或日期時間偏移量,并且它們考慮了諸如閏年之類的東西。您可以傳入負值進行減法。
Add 方法將 TimeSpan 添加到 DateTime 或 DateTimeOffset 。+ 運算符重載以執(zhí)行相同的工作:
TimeSpan ts=TimeSpan.FromMinutes (90);
Console.WriteLine (dt.Add (ts));
Console.WriteLine (dt + ts); // same as above
您還可以從日期時間/日期時間偏移量中減去時間跨度,并從另一個日期時間/日期時間偏移量中減去另一個日期時間/日期時間偏移量。后者給你一個時間跨度:
DateTime thisYear=new DateTime (2015, 1, 1);
DateTime nextYear=thisYear.AddYears (1);
TimeSpan oneYear=nextYear - thisYear;
在日期時間上調(diào)用 ToString 會將結(jié)果格式設(shè)置為(所有數(shù)字)后跟(包括秒)。例如:
11/11/2019 11:50:30 AM
默認情況下,操作系統(tǒng)的控制面板確定日、月或年是先排嗎;使用前導零;以及是使用 12 小時還是 24 小時時間。
在 DateTimeOffset 上調(diào)用 ToString 是相同的,只是還返回了偏移量:
11/11/2019 11:50:30 AM -06:00
ToShortDateString和ToLongDateString方法只返回日期部分。長日期格式也由控制面板決定;一個例子是“2015年11月11日,星期三”。ToShortTimeString和ToLongTimeString只返回時間部分,例如17:10:10(前者不包括秒)。
這四種剛剛描述的方法實際上是四種不同的快捷方式。ToString 重載以接受格式字符串和提供程序,允許您指定各種選項并控制區(qū)域設(shè)置的應(yīng)用方式。我們在中對此進行了描述。
如果區(qū)域性設(shè)置與設(shè)置格式設(shè)置時有效的區(qū)域性設(shè)置不同,則可能會錯誤分析日期時間和日期時間偏移量 s。通過將 ToString 與忽略區(qū)域性設(shè)置(如“o”)的格式字符串結(jié)合使用,可以避免此問題:
DateTime dt1=DateTime.Now;
string cannotBeMisparsed=dt1.ToString ("o");
DateTime dt2=DateTime.Parse (cannotBeMisparsed);
靜態(tài) Parse / TryParse 和 ParseExact / TryParseExact 方法與 ToString 相反,將字符串轉(zhuǎn)換為 DateTime 或 DateTimeOffset 。這些方法也會重載以接受格式提供程序。Try * 方法返回 false 而不是拋出 FormatException 。
由于 DateTime 和 DateTimeOffset 是結(jié)構(gòu)體,因此它們本質(zhì)上不可為空。當您需要可為空性時,有兩種方法可以解決此問題:
可為 null 的類型通常是最佳方法,因為編譯器有助于防止錯誤。DateTime.MinValue 對于向后兼容在 C# 2.0(引入可為 null 的值類型時)之前編寫的代碼非常有用。
在 DateTime.MinValue 上調(diào)用 ToUniversalTime 或 ToLocalTime 可能會導致它不再是 DateTime.MinValue(取決于您在 GMT 的哪一邊)。如果你在格林威治標準時間(英格蘭,夏令時之外)是正確的,那么問題根本不會出現(xiàn),因為本地時間和UTC時間是相同的。這是你對英國冬天的補償!
DateOnly 和 TimeOnly 結(jié)構(gòu)(從 .NET 6 開始)日期或時間的情況。
DateOnly 與 DateTime 類似,但沒有時間部分。DateOnly 也缺少 DateTimeKind ;實際上,它始終是未指定的,并且沒有本地或UTC的概念。DateOnly 的歷史替代方案是使用零時間(午夜)的 DateTime。這種方法的困難在于,當非零時間進入代碼時,相等比較會失敗。
TimeOnly 與 日期時間 ,但沒有日期組件。TimeOnly 用于捕獲一天中的時間,適用于記錄鬧鐘時間或營業(yè)時間等應(yīng)用。
日期時間在處理時區(qū)方面非常簡單。在內(nèi)部,它使用兩條信息存儲日期時間:
比較兩個日期時間實例時,僅比較它們的值;他們的日期時間種類被忽略:
DateTime dt1=new DateTime (2000, 1, 1, 10, 20, 30, DateTimeKind.Local);
DateTime dt2=new DateTime (2000, 1, 1, 10, 20, 30, DateTimeKind.Utc);
Console.WriteLine (dt1==dt2); // True
DateTime local=DateTime.Now;
DateTime utc=local.ToUniversalTime();
Console.WriteLine (local==utc); // False
實例方法將轉(zhuǎn)換為通用時間/本地時間。這些設(shè)置應(yīng)用計算機的當前時區(qū)設(shè)置,并返回新的日期時間,其日期時間類型為 UTC 或本地。如果在已經(jīng)是 UTC 的日期時間上調(diào)用 ToUniversalTime,或者在已經(jīng)是 Local 的日期時間上調(diào)用 ToLocalTime,則不會發(fā)生轉(zhuǎn)換。但是,如果您在未指定的日期時間上調(diào)用 ToUniversalTime 或 ToLocalTime,您將獲得轉(zhuǎn)換。
您可以使用靜態(tài) DateTime.SpecifyKind 方法構(gòu)造一個僅在 Kind 上不同于另一個的 DateTime:
DateTime d=new DateTime (2015, 12, 12); // Unspecified
DateTime utc=DateTime.SpecifyKind (d, DateTimeKind.Utc);
Console.WriteLine (utc); // 12/12/2015 12:00:00 AM
在內(nèi)部,DateTimeOffset 包含一個 DateTime 字段(其值始終采用 UTC),以及一個 16 位整數(shù)字段,用于表示 UTC 偏移量(以分鐘為單位)。比較只看 (UTC) 日期時間 ;偏移量主要用于格式化。
ToUniversalTime / ToLocalTime 方法返回一個 DateTimeOffset 表示相同的時間點,但具有 UTC 或本地偏移量。與 DateTime 不同,這些方法不會影響基礎(chǔ)日期/時間值,只影響偏移量:
DateTimeOffset local=DateTimeOffset.Now;
DateTimeOffset utc=local.ToUniversalTime();
Console.WriteLine (local.Offset); // -06:00:00 (in Central America)
Console.WriteLine (utc.Offset); // 00:00:00
Console.WriteLine (local==utc); // True
若要在比較中包含偏移量,必須使用 EqualsExact 方法:
Console.WriteLine (local.EqualsExact (utc)); // False
類提供有關(guān)時區(qū)名稱、UTC 偏移量和夏令時規(guī)則的信息。
靜態(tài) TimeZone.CurrentTimeZone 方法返回一個時區(qū):
TimeZone zone=TimeZone.CurrentTimeZone;
Console.WriteLine (zone.StandardName); // Pacific Standard Time
Console.WriteLine (zone.DaylightName); // Pacific Daylight Time
方法返回給定年份的特定夏令時信息:
DaylightTime day=zone.GetDaylightChanges (2019);
Console.WriteLine (day.Start.ToString ("M")); // 10 March
Console.WriteLine (day.End.ToString ("M")); // 03 November
Console.WriteLine (day.Delta); // 01:00:00
靜態(tài) TimeZoneInfo.Local 方法基于當前本地設(shè)置返回 TimeZoneInfo 對象。下面演示了在加利福尼亞州運行的結(jié)果:
TimeZoneInfo zone=TimeZoneInfo.Local;
Console.WriteLine (zone.StandardName); // Pacific Standard Time
Console.WriteLine (zone.DaylightName); // Pacific Daylight Time
IsDaylightSavingTime 和 GetUtcOffset 方法的工作方式如下:
DateTime dt1=new DateTime (2019, 1, 1); // DateTimeOffset works, too
DateTime dt2=new DateTime (2019, 6, 1);
Console.WriteLine (zone.IsDaylightSavingTime (dt1)); // True
Console.WriteLine (zone.IsDaylightSavingTime (dt2)); // False
Console.WriteLine (zone.GetUtcOffset (dt1)); // -08:00:00
Console.WriteLine (zone.GetUtcOffset (dt2)); // -07:00:00
您可以通過使用區(qū)域 ID 調(diào)用 FindSystemTimeZoneById 來獲取世界上任何時區(qū)的 TimeZoneInfo。我們將轉(zhuǎn)向西澳大利亞州,原因很快就會變得清晰:
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
屬性對應(yīng)于傳遞給 FindSystemTimeZoneById 的值。靜態(tài) GetSystemTimeZones 方法返回所有世界時區(qū);因此,您可以列出所有有效的區(qū)域 ID 字符串,如下所示:
foreach (TimeZoneInfo z in TimeZoneInfo.GetSystemTimeZones())
Console.WriteLine (z.Id);
您還可以通過調(diào)用 TimeZoneInfo.CreateCustomTimeZone 創(chuàng)建自定義時區(qū)。由于 TimeZoneInfo 是不可變的,因此必須將所有相關(guān)數(shù)據(jù)作為方法參數(shù)傳入。
您可以通過調(diào)用 ToSerializedString 將預(yù)定義或自定義時區(qū)序列化為(半)人類可讀字符串,并通過調(diào)用 TimeZoneInfo.FromSerializedString 對其進行反序列化。
靜態(tài) ConvertTime 方法將日期時間或日期時間偏移量從一個時區(qū)轉(zhuǎn)換為另一個時區(qū)。您可以只包含目標時區(qū)信息,也可以同時包含源和目標時區(qū)信息對象。您還可以使用方法直接從 UTC 轉(zhuǎn)換或轉(zhuǎn)換為 UTC 轉(zhuǎn)換時間到UTC 。
為了使用夏令時,時區(qū)信息提供了以下附加方法:
您無法從指示夏令時開始和結(jié)束的時區(qū)信息獲取簡單日期。相反,您必須調(diào)用 GetAdjustRules ,這將返回適用于所有年份的所有夏令時規(guī)則的聲明性摘要。每個規(guī)則都有一個日期開始和日期結(jié)束,指示規(guī)則有效的日期范圍:
foreach (TimeZoneInfo.AdjustmentRule rule in wa.GetAdjustmentRules())
Console.WriteLine ("Rule: applies from " + rule.DateStart +
" to " + rule.DateEnd);
西澳大利亞州在2006年季首次引入夏令時(然后在2009年取消了它)。這需要第一年的特殊規(guī)則;因此,有兩個規(guī)則:
Rule: applies from 1/01/2006 12:00:00 AM to 31/12/2006 12:00:00 AM
Rule: applies from 1/01/2007 12:00:00 AM to 31/12/2009 12:00:00 AM
每個調(diào)整規(guī)則都有一個 TimeSpan 類型的 DaylightDelta 屬性(幾乎每種情況下都是一小時)和名為 DaylightTransitionStart 和 DaylightTransitionEnd 的屬性。后兩者的類型為 時區(qū)信息.過渡時間 ,它具有以下屬性:
public bool IsFixedDateRule { get; }
public DayOfWeek DayOfWeek { get; }
public int Week { get; }
public int Day { get; }
public int Month { get; }
public DateTime TimeOfDay { get; }
轉(zhuǎn)換時間有些復(fù)雜,因為它需要表示固定日期和浮動日期。浮動日期的一個例子是“三月的最后一個星期日”。以下是解釋過渡時間的規(guī)則:
在最后一種情況下,周是指每月的一周,“5”表示上周。我們可以通過枚舉 wa 時區(qū)的調(diào)整規(guī)則來證明這一點:
foreach (TimeZoneInfo.AdjustmentRule rule in wa.GetAdjustmentRules())
{
Console.WriteLine ("Rule: applies from " + rule.DateStart +
" to " + rule.DateEnd);
Console.WriteLine (" Delta: " + rule.DaylightDelta);
Console.WriteLine (" Start: " + FormatTransitionTime
(rule.DaylightTransitionStart, false));
Console.WriteLine (" End: " + FormatTransitionTime
(rule.DaylightTransitionEnd, true));
Console.WriteLine();
}
在 格式過渡時間 ,我們尊重剛才描述的規(guī)則:
static string FormatTransitionTime (TimeZoneInfo.TransitionTime tt,
bool endTime)
{
if (endTime && tt.IsFixedDateRule
&& tt.Day==1 && tt.Month==1
&& tt.TimeOfDay==DateTime.MinValue)
return "-";
string s;
if (tt.IsFixedDateRule)
s=tt.Day.ToString();
else
s="The " +
"first second third fourth last".Split() [tt.Week - 1] +
" " + tt.DayOfWeek + " in";
return s + " " + DateTimeFormatInfo.CurrentInfo.MonthNames [tt.Month-1]
+ " at " + tt.TimeOfDay.TimeOfDay;
}
如果使用 DateTimeOffset 或 UTC DateTime,則相等比較不受夏令時影響的阻礙。但是對于本地日期時間,夏令時可能會出現(xiàn)問題。
我們可以將規(guī)則總結(jié)如下:
IsDaylightSavingTime 告訴您給定的本地 DateTime 是否受夏令時約束。UTC 時間總是返回 false :
Console.Write (DateTime.Now.IsDaylightSavingTime()); // True or False
Console.Write (DateTime.UtcNow.IsDaylightSavingTime()); // Always False
假設(shè) dto 是一個 DateTimeOffset ,下面的表達式也做同樣的事情:
dto.LocalDateTime.IsDaylightSavingTime
夏令時的結(jié)束給使用本地時間的算法帶來了特別的復(fù)雜性,因為當時鐘倒流時,同一小時(或更準確地說,Delta )會重復(fù)。
您可以通過首先調(diào)用 ToUniversalTime 來可靠地比較任意兩個日期時間。當(且僅當)其中一個具有 DateTimeKind 未指定 時,此策略將失敗。是支持量的另一個原因。
格式化;解析意味著從字符串在編程中,在各種情況下,經(jīng)常需要格式化或解析。因此,.NET 提供了多種機制:
ToString 和 Parse
這些方法為許多類型提供默認功能。
格式提供程序
這些清單作為接受格式和/或的附加 ToString(和 Parse)方法。格式提供商具有高度的靈活性和區(qū)域性意識。.NET 包括數(shù)字類型和日期時間/日期時間偏移量的格式提供程序。
XmlConvert
這是一個靜態(tài)類,其方法在遵循 XML 標準的同時進行格式化和分析。當您需要區(qū)域性獨立性或想要搶占錯誤分析時,XmlConvert 對于常規(guī)用途轉(zhuǎn)換也很有用。XmlConvert 支持數(shù)值類型、布爾值、日期時間、日期時間偏移量、時間跨度和 Guid。
類型轉(zhuǎn)換器
這些目標設(shè)計器和 XAML 分析程序。
在本節(jié)中,我們將討論前兩種機制,特別關(guān)注格式提供程序。然后我們描述 XmlConvert、類型轉(zhuǎn)換器和其他轉(zhuǎn)換機制。
最簡單的格式設(shè)置機制是 ToString 方法。它對所有簡單值類型(布爾值、日期時間、日期時間偏移量、時間跨度、Guid 和所有數(shù)值類型)給出有意義的輸出。對于反向操作,以下每種類型都定義一個靜態(tài) Parse 方法:
string s=true.ToString(); // s="True"
bool b=bool.Parse (s); // b=true
如果分析失敗,則會引發(fā)格式異常。許多類型還定義了 TryParse 方法,如果轉(zhuǎn)換失敗,該方法返回 false,而不是引發(fā)異常:
bool failure=int.TryParse ("qwerty", out int i1);
bool success=int.TryParse ("123", out int i2);
如果您不關(guān)心輸出并且只想測試解析是否成功,則可以使用丟棄:
bool success=int.TryParse ("123", out int _);
如果預(yù)計會出現(xiàn)錯誤,則調(diào)用 TryParse 比在異常處理塊中調(diào)用 Parse 更快、更優(yōu)雅。
日期時間(偏移量)上的解析和嘗試解析方法以及數(shù)值類型遵循本地區(qū)域性設(shè)置;可以通過指定 CultureInfo 對象來更改此設(shè)置。指定固定區(qū)域性通常是一個好主意。例如,將 “1.234” 解析為雙精度數(shù),在德國得到 1234:
Console.WriteLine (double.Parse ("1.234")); // 1234 (In Germany)
這是因為在德國,句點表示千位分隔符而不是小數(shù)點。指定可解決此問題:
double x=double.Parse ("1.234", CultureInfo.InvariantCulture);
這同樣適用于調(diào)用 ToString() 時:
string x=1.234.ToString (CultureInfo.InvariantCulture);
有時,您需要更好地控制格式化和分析的發(fā)生方式。有幾十種方法可以格式化日期時間(偏移量) ,例如。格式提供程序允許對格式設(shè)置和分析進行廣泛控制,并且支持數(shù)字類型和日期/時間。用戶界面控件還使用格式提供程序進行格式設(shè)置和分析。
使用格式提供程序的網(wǎng)關(guān)是 IFormatable 。所有數(shù)值類型和日期時間(偏移量)都實現(xiàn)此接口:
public interface IFormattable
{
string ToString (string format, IFormatProvider formatProvider);
}
第一個參數(shù)是;第二個是。格式字符串提供說明;格式提供程序確定如何翻譯說明。例如:
NumberFormatInfo f=new NumberFormatInfo();
f.CurrencySymbol="$$";
Console.WriteLine (3.ToString ("C", f)); // $$ 3.00
此處,“C”是指示的格式字符串,而 NumberFormatInfo 對象是確定如何呈現(xiàn)貨幣和其他數(shù)字表示形式的格式提供程序。這一機制允許全球化。
數(shù)字和日期的所有格式字符串都列在中。
如果指定空格式字符串或提供程序,則應(yīng)用默認值。默認格式提供程序是 CultureInfo.CurrentCulture ,除非重新分配,否則它反映計算機的運行時控制面板設(shè)置。例如,在此計算機上:
Console.WriteLine (10.3.ToString ("C", null)); // .30
為方便起見,大多數(shù)類型重載 ToString,以便您可以省略空:
Console.WriteLine (10.3.ToString ("C")); // $10.30
Console.WriteLine (10.3.ToString ("F4")); // 10.3000 (Fix to 4 D.P.)
在 DateTime ( 偏移量 ) 或不帶參數(shù)的數(shù)值類型上調(diào)用 ToString 等效于使用具有空格式字符串的默認格式提供程序。
.NET 定義了三個格式提供程序(它們都實現(xiàn) IFormatProvider):
NumberFormatInfo
DateTimeFormatInfo
CultureInfo
所有枚舉類型也是可格式化的,盡管沒有特殊的 IFormatProvider 類。
在格式提供程序的上下文中,CultureInfo 充當其他兩個格式提供程序的間接機制,返回適用于區(qū)域性區(qū)域設(shè)置的 NumberFormatInfo 或 DateTimeFormatInfo 對象。
在下面的示例中,我們請求特定的區(qū)域性(reat ritain中的英語語言):
CultureInfo uk=CultureInfo.GetCultureInfo ("en-GB");
Console.WriteLine (3.ToString ("C", uk)); // £3.00
這將使用適用于 en-GB 區(qū)域性的默認 NumberFormatInfo 對象執(zhí)行。
下一個示例使用固定區(qū)域性設(shè)置日期時間的格式。無論計算機的設(shè)置如何,固定區(qū)域性始終相同:
DateTime dt=new DateTime (2000, 1, 2);
CultureInfo iv=CultureInfo.InvariantCulture;
Console.WriteLine (dt.ToString (iv)); // 01/02/2000 00:00:00
Console.WriteLine (dt.ToString ("d", iv)); // 01/02/2000
固定區(qū)域性基于美國文化,具有以下區(qū)別:
在下一個示例中,我們實例化一個 NumberFormatInfo 并將組分隔符從逗號更改為空格。然后,我們使用它來將數(shù)字格式化為小數(shù)點后三位:
NumberFormatInfo f=new NumberFormatInfo ();
f.NumberGroupSeparator=" ";
Console.WriteLine (12345.6789.ToString ("N3", f)); // 12 345.679
NumberFormatInfo 或 DateTimeFormatInfo 的初始設(shè)置基于固定區(qū)域性。但是,有時選擇不同的起點更有用。為此,您可以克隆現(xiàn)有的格式提供程序:
NumberFormatInfo f=(NumberFormatInfo)
CultureInfo.CurrentCulture.NumberFormat.Clone();
克隆的格式提供程序始終是可寫的,即使原始格式提供程序是只讀的。
復(fù)合格式字符串允許您將變量替換與格式字符串組合在一起。靜態(tài)字符串。格式方法接受復(fù)合格式字符串(我們在中對此進行了說明):
string composite="Credit={0:C}";
Console.WriteLine (string.Format (composite, 500)); // Credit=$500.00
控制臺類本身重載其 Write 和 WriteLine 方法以接受復(fù)合格式字符串,從而允許我們稍微縮短此示例:
Console.WriteLine ("Credit={0:C}", 500); // Credit=0.00
您還可以將復(fù)合格式字符串附加到 StringBuilder(通過 AppendFormat)和用于 I/O 的文本編寫器(參見)。
字符串。格式接受可選的格式提供程序。一個簡單的應(yīng)用程序是在傳入格式提供程序時對任意對象調(diào)用 ToString:
string s=string.Format (CultureInfo.InvariantCulture, "{0}", someObject);
這等效于以下內(nèi)容:
string s;
if (someObject is IFormattable)
s=((IFormattable)someObject).ToString (null,
CultureInfo.InvariantCulture);
else if (someObject==null)
s="";
else
s=someObject.ToString();
沒有用于通過格式提供程序進行分析的標準接口。相反,每個參與類型都會重載其靜態(tài) Parse(和 TryParse)方法,以接受格式提供程序和(可選)NumberStyles 或 DateTimeStyles 枚舉。
NumberStyles 和 DateTimeStyles 控制解析的工作方式:它們允許您指定括號或貨幣符號是否可以出現(xiàn)在輸入字符串中。(默認情況下,這兩個問題的答案都是。例如:
int error=int.Parse ("(2)"); // Exception thrown
int minusTwo=int.Parse ("(2)", NumberStyles.Integer |
NumberStyles.AllowParentheses); // OK
decimal fivePointTwo=decimal.Parse ("£5.20", NumberStyles.Currency,
CultureInfo.GetCultureInfo ("en-GB"));
下一節(jié)列出了所有 NumberStyles 和 DateTimeStyles 成員以及每種類型的默認分析規(guī)則。
所有格式提供程序都實現(xiàn) IFormatProvider:
public interface IFormatProvider { object GetFormat (Type formatType); }
此方法的目的是提供間接性 - 這是允許 CultureInfo 遵從適當?shù)?NumberFormatInfo 或 DateTimeInfo 對象來完成工作的原因。
通過實現(xiàn) IFormatProvider 以及 ICustomFormatter,您還可以編寫自己的格式提供程序,與現(xiàn)有類型結(jié)合使用。 ICustomFormatter 定義了單個方法,如下所示:
string Format (string format, object arg, IFormatProvider formatProvider);
以下自定義格式提供程序?qū)?shù)字寫為單詞:
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),
StringComparison.InvariantCulture);
if (i==-1) continue;
if (result.Length > 0) result.Append (' ');
result.Append (_numberWords[i]);
}
return result.ToString();
}
}
請注意,在 Format 方法中,我們使用了字符串。格式 - 使用固定文化 - 將輸入數(shù)字轉(zhuǎn)換為字符串。在arg上調(diào)用ToString()會更簡單,但是后來會使用CurrentCulture。需要固定區(qū)域性的原因在后面幾行中很明顯:
int i="0123456789-.".IndexOf (digit);
在這里,數(shù)字字符串僅包含字符 0123456789- 至關(guān)重要。而不是這些的任何國際化版本。
以下是使用 WordyFormatProvider 的示例:
double n=-123.45;
IFormatProvider fp=new WordyFormatProvider();
Console.WriteLine (string.Format (fp, "{0:C} in words is {0:W}", n));
// -$123.45 in words is minus one two three point four five
只能在復(fù)合格式字符串中使用自定義格式提供程序。
標準格式字符串控制如何將數(shù)值類型或日期時間/日期時間偏移量轉(zhuǎn)換為字符串。有兩種格式字符串:
標準格式字符串
有了這些,您可以提供一般指導。標準格式字符串由單個字母組成,后跟一個數(shù)字(其含義取決于字母)。一個例子是 “C” 或 “F2” .
自定義格式字符串
有了這些,您可以使用模板對每個字符進行微觀管理。一個例子是 “0:#.000E+00” 。
自定義格式字符串與自定義格式提供程序無關(guān)。
列出了所有標準數(shù)字格式字符串。
標準數(shù)字格式字符串 | ||||
信 | 意義 | 示例輸入 | 結(jié)果 | 筆記 |
G 或 g | “一般” | 1.2345, “G” 0.00001, “G” 0.00001, “g” 1.2345, “G3” 12345, “G3” | 1.2345 1E-05 1E-05 1.23 1.23E04 | 對于小數(shù)字或大數(shù)字,切換到指數(shù)表示法。 G3 將精度限制為(點之前 + 點后)。 |
F | 定點 | 2345.678, “F2” 2345.6, “F2” | 2345.68 2345.60 | F2 舍入到小數(shù)點后兩位。 |
N | 帶的固定點(“數(shù)字”) | 2345.678, “N2” 2345.6, “N2” | 2,345.68 2,345.60 | 如上所述,使用組(1,000s)分隔符(格式提供程序的詳細信息)。 |
D | 帶前導零的焊盤 | 123, “D5” 123, “D1” | 00123 123 | 僅適用于整型。 D5 墊子左至五位數(shù);不會截斷。 |
E 或 E | 強制指數(shù)表示法 | 56789, “E” 56789, “E” 56789, “E2” | 5.678900E+004 5.678900e+004 5.68E+004 | 六位數(shù)默認精度。 |
C | 貨幣 | 1.2, “C” 1.2, “C4” | .20 .2000 | 不帶數(shù)字的 C 使用格式提供程序的默認 D.P. 編號。 |
P | 百分之 | .503, “P” .503, “P0” | 50.30% 50% | 使用格式提供程序中的符號和布局。可以選擇覆蓋小數(shù)位。 |
X 或 x | 十六進制 | 47, “X” 47, “x” 47, “X4” | 2樓 2樓 002樓 | X 表示大寫十六進制數(shù)字;x 表示小寫十六進制數(shù)字。僅限積分。 |
R 或 G9/G17 | 往返 | 1樓 / 3樓,“R” | 0.333333343 | 使用 R 表示 BigInteger,G17 表示雙精度,或 G9 表示浮點數(shù)。 |
不提供數(shù)字格式字符串(或空或空白字符串)等效于使用“G”標準格式字符串后跟無數(shù)字。這將表現(xiàn)出以下行為:
剛才描述的自動舍入通常是有益的,不會被注意到。但是,如果您需要往返號碼,則可能會造成麻煩;換句話說,將其轉(zhuǎn)換為字符串并再次(可能重復(fù))回來,同時保持值相等。因此,存在 R、G17 和 G9 格式字符串來規(guī)避這種隱式舍入。
列出了自定義數(shù)字格式字符串。
自定義數(shù)字格式字符串 | ||||
規(guī)范 | 意義 | 示例輸入 | 結(jié)果 | 筆記 |
# | 數(shù)字占位符 | 12.345, “.##” 12.345, “.####” | 12.35 12.345 | 限制 DP 之后的數(shù)字 |
0 | 零占位符 | 12.345, “.00” 12.345, “.0000” 99, “000.00” | 12.35 12.3450 099.00 | 如上所述,但在 D.P. 之前和之后也用零填充。 |
. | 小數(shù)點 | 表示 DP 實際符號來自 數(shù)字格式信息 。 | ||
, | 組分隔符 | 1234, “#,###,###” 1234, “0,000,000” | 1,234 0,001,234 | 符號來自 數(shù)字格式信息 。 |
, (同上) | 乘數(shù) | 1000000, “#,” 1000000, “#,, | 1000 1 | 如果逗號位于 D.P. 的末尾或之前,則它充當乘數(shù) - 將結(jié)果除以 1,000、1,000,000 等。 |
% | 百分比表示法 | 0.6, "00%" | 60% | 首先乘以 100,然后替換從 獲得的百分比符號。 |
E0, e0, E+0, e+0 E-0, e-0 | 指數(shù)表示法 | 1234, “0E0” 1234, “0E+0” 1234, “0.00E00” 1234, “0.00e00” | 1E3 1E+3 1.23E03 1.23E03 | |
\ | 字面人物引用 | 50, @"\#0" | #50 | 與字符串上的 @ 前綴結(jié)合使用,或使用 \。 |
“xx”“xx” | 文字字符串引用 | 50, "0 '...'" | 50 ... | |
; | 節(jié)分隔符 | 15, “#;(#);零” | 15 | (如果為陽性。 |
-5, “#;(#);零” | (5) | (如果為負數(shù)。 | ||
0, “#;(#);零” | 零 | (如果為零。 | ||
任何其他字符 | 字面 | 35.2, “>35.2, “$0 .00c”< .00c” | .20c |
每個數(shù)值類型定義一個接受 NumberStyles 參數(shù)的靜態(tài) Parse 方法。NumberStyles 是一個標志枚舉,可用于確定在字符串轉(zhuǎn)換為數(shù)值類型時如何讀取字符串。它具有以下可組合成員:
AllowLeadingWhite AllowTrailingWhite
AllowLeadingSign AllowTrailingSign
AllowParentheses AllowDecimalPoint
AllowThousands AllowExponent
AllowCurrencySymbol AllowHexSpecifier
NumberStyles 還定義了以下復(fù)合成員:
None Integer Float Number HexNumber Currency Any
除了 None 之外,所有復(fù)合值都包括 AllowLeadingWhite 和 AllowTrailingWhite 。顯示了他們剩余的妝容,其中最有用的三個強調(diào)了。
在未指定任何標志的情況下調(diào)用 Parse 時,將應(yīng)用 中所示的默認值。
如果不需要 中所示的默認值,則必須顯式指定 :
int thousand=int.Parse ("3E8", NumberStyles.HexNumber);
int minusTwo=int.Parse ("(2)", NumberStyles.Integer |
NumberStyles.AllowParentheses);
double aMillion=double.Parse ("1,000,000", NumberStyles.Any);
decimal threeMillion=decimal.Parse ("3e6", NumberStyles.Any);
decimal fivePointTwo=decimal.Parse ("$5.20", NumberStyles.Currency);
由于我們未指定格式提供程序,因此此示例使用本地貨幣符號、組分隔符、小數(shù)點等。下一個示例經(jīng)過硬編碼,可與歐元符號和貨幣的空白組分隔符一起使用:
NumberFormatInfo ni=new NumberFormatInfo();
ni.CurrencySymbol="";
ni.CurrencyGroupSeparator=" ";
double million=double.Parse ("1 000 000", NumberStyles.Currency, ni);
日期時間/日期時間偏移量的格式字符串可以根據(jù)它們是否支持區(qū)域性和格式提供程序設(shè)置分為兩組。 列出了這樣做的那些; 列出了那些沒有的。示例輸出來自以下 DateTime 的格式設(shè)置(在 中為使用):
new DateTime (2000, 1, 2, 17, 18, 19);
區(qū)分區(qū)域性的日期/時間格式字符串 | ||
格式字符串 | 意義 | 示例輸出 |
d | 短日期 | 01/02/2000 |
D | 長日期 | Sunday, 02 January 2000 |
t | 時間短 | 17:18 |
T | 乆 | 17:18:19 |
f | 長日期+短時間 | Sunday, 02 January 2000 17:18 |
F | 長日期+長時間 | Sunday, 02 January 2000 17:18:19 |
g | 短日期+短時間 | 01/02/2000 17:18 |
G(默認) | 短日期 + 長時間 | 01/02/2000 17:18:19 |
米,米 | 月和日 | 02 一月 |
y, y | 年和月 | January 2000 |
不區(qū)分區(qū)域性的日期/時間格式字符串 | |||
格式字符串 | 意義 | 示例輸出 | 筆記 |
o | 可往返跳閘 | 2000-01-02T17:18:19.0000000 | 將附加時區(qū)信息,除非 日期時間種類 未。 |
r , R | RFC 1123 標準 | 周日, 02 一月 2000 17:18:19 GMT | 您必須使用 顯式轉(zhuǎn)換為 UTC。 |
s | 可排序;ISO 8601 認證 | 2000-01-02T17:18:19 | 與基于文本的排序兼容。 |
u | “通用”可排序 | 2000-01-02 17:18:19Z | 與上面類似;必須顯式轉(zhuǎn)換為 UTC。 |
U | 世界協(xié)調(diào)時 | Sunday, 02 January 2000 17:18:19 | 長日期 + 短時間,轉(zhuǎn)換為 UTC。 |
格式字符串 “r” 、 “R” 和 “u” 發(fā)出一個暗示 UTC 的后綴;但是,它們不會自動將本地轉(zhuǎn)換為 UTC 日期時間(因此您必須自己進行轉(zhuǎn)換)。具有諷刺意味的是,“U”會自動轉(zhuǎn)換為UTC,但不寫時區(qū)后綴!事實上,“o”是組中唯一可以在沒有干預(yù)的情況下編寫明確日期時間的格式說明符。
DateTimeFormatInfo 還支持自定義格式字符串:這些類似于數(shù)字自定義格式字符串。該列表內(nèi)容廣泛,可在Microsoft的文檔中在線獲得。下面是自定義格式字符串的示例:
yyyy-MM-dd HH:mm:ss
將月份或日期放在首位的字符串不明確,很容易被錯誤解析,尤其是在您有全球客戶的情況下。這在用戶界面控件中不是問題,因為解析時和格式化時相同的設(shè)置有效。但是,例如,在寫入文件時,日/月錯誤解析可能是一個真正的問題。有兩種解決方案:
第二種方法更可靠,特別是如果您選擇將四位數(shù)年份放在首位的格式:此類字符串更難被另一方錯誤解析。此外,使用年份優(yōu)先格式(例如“o”)格式化的字符串可以與本地格式的字符串一起正確解析 - 更像“通用捐贈者”。(用“s”或“u”格式設(shè)置的日期還有可排序的進一步好處。
為了說明這一點,假設(shè)我們生成一個不區(qū)分區(qū)域性的 DateTime 字符串 :
string s=DateTime.Now.ToString ("o");
“o”格式字符串在輸出中包含毫秒。以下自定義格式字符串給出與“o”相同的結(jié)果,但沒有毫秒:
yyyy-MM-ddTHH:mm:ss K
我們可以通過兩種方式重新解析它。ParseExact 要求嚴格遵守指定的格式字符串:
DateTime dt1=DateTime.ParseExact (s, "o", null);
(您可以使用 XmlConvert 的 ToString 和 ToDateTime 方法獲得類似的結(jié)果。
然而,Parse隱式地接受“o”格式和CurrentCulture:
DateTime dt2=DateTime.Parse (s);
這適用于 日期時間和日期時間偏移量 。
如果您知道要解析的字符串的格式,則通常最好使用 ParseExact 。這意味著,如果字符串格式不正確,則會引發(fā)異常,這通常比冒著錯誤解析日期的風險要好。
DateTimeStyles 是一個標志枚舉,在對 DateTime ( 偏移量) 上調(diào)用 Parse 時提供附加指令。以下是其成員:
None,
AllowLeadingWhite, AllowTrailingWhite, AllowInnerWhite,
AssumeLocal, AssumeUniversal, AdjustToUniversal,
NoCurrentDateDefault, RoundTripKind
還有一個復(fù)合成員,AllowWhiteSpaces:
AllowWhiteSpaces=AllowLeadingWhite | AllowTrailingWhite | AllowInnerWhite
默認值為 無 。這意味著通常禁止使用額外的空格(作為標準 DateTime 模式一部分的空格除外)。
假設(shè)本地和假設(shè)通用如果字符串沒有時區(qū)后綴(如 Z 或 +9:00)則適用。AdjustToUniversal 仍支持時區(qū)后綴,但隨后使用當前區(qū)域設(shè)置轉(zhuǎn)換為 UTC。
If you parse a string comprising a time but no date, today’s date is applied by default. If you apply the NoCurrentDateDefault flag, however, it instead uses 1st January 0001.
在第 的中,我們描述了枚舉值的格式化和解析。 列出了每個格式字符串以及將其應(yīng)用于以下表達式的結(jié)果:
Console.WriteLine (System.ConsoleColor.Red.ToString (formatString));
枚舉格式字符串 | |||
格式字符串 | 意義 | 示例輸出 | 筆記 |
G 或 g | “一般” | 紅 | 違約 |
F 或 f | 視為存在標志屬性 | 紅 | 適用于組合成員,即使枚舉沒有標志屬性 |
D 或 d | 十進制值 | 12 | 檢索基礎(chǔ)積分值 |
X 或 x | 十六進制值 | 0000000C | 檢索基礎(chǔ)積分值 |
在前兩節(jié)中,我們介紹了格式提供程序 - 。NET 用于格式化和分析的主要機制。其他重要的轉(zhuǎn)換機制分散在各種類型和命名空間中。有些與字符串進行轉(zhuǎn)換,有些則進行其他類型的轉(zhuǎn)換。在本節(jié)中,我們將討論以下主題:
.NET 調(diào)用以下類型:
靜態(tài) Convert 類定義將每個基類型轉(zhuǎn)換為每個其他基類型的方法。不幸的是,這些方法中的大多數(shù)都是無用的:它們要么拋出異常,要么與隱式強制轉(zhuǎn)換一起冗余。但是,在混亂中,有一些有用的方法,在以下各節(jié)中列出。
所有基類型(顯式地)都實現(xiàn)了 IConvertible,它定義了轉(zhuǎn)換為所有其他基類型的方法。在大多數(shù)情況下,這些方法中的每一個的實現(xiàn)只是在 轉(zhuǎn)換 .在極少數(shù)情況下,編寫接受 IConvertible 類型的參數(shù)的方法可能很有用。
在第 中,我們了解了隱式和顯式強制轉(zhuǎn)換如何允許您在數(shù)值類型之間進行轉(zhuǎn)換。總結(jié):
鑄件針對效率進行了優(yōu)化;因此,它們不適合的數(shù)據(jù)。從實數(shù)轉(zhuǎn)換為整數(shù)時,這可能是一個問題,因為通常您希望而不是截斷。Convert 的數(shù)值轉(zhuǎn)換方法正好解決了這個問題——它們總是:
double d=3.9;
int i=Convert.ToInt32 (d); // i==4
轉(zhuǎn)換使用,將中點值捕捉為偶數(shù)(這可以防止正偏差或負偏差)。如果銀行家的四舍五入是一個問題,首先調(diào)用實數(shù)的 Math.Round:這接受一個額外的參數(shù),允許您控制中點舍入。
隱藏在方法中的是解析另一個基數(shù)的重載:To(integral-type)
int thirty=Convert.ToInt32 ("1E", 16); // Parse in hexadecimal
uint five=Convert.ToUInt32 ("101", 2); // Parse in binary
第二個參數(shù)指定基數(shù)。它可以是你喜歡的任何基數(shù)——只要是 2、8、10 或 16!
有時,您需要從一種類型轉(zhuǎn)換為另一種類型,但直到運行時您才知道這些類型是什么。為此,Convert 類提供了一個 ChangeType 方法:
public static object ChangeType (object value, Type conversionType);
源和目標類型必須是“基”類型之一。ChangeType 還接受可選的 IFormatProvider 參數(shù)。下面是一個示例:
Type targetType=typeof (int);
object source="42";
object result=Convert.ChangeType (source, targetType);
Console.WriteLine (result); // 42
Console.WriteLine (result.GetType()); // System.Int32
這可能有用的一個示例是編寫可以使用多種類型的反序列化程序。它還可以將任何枚舉轉(zhuǎn)換為其整數(shù)類型(參見中的)。
ChangeType 的一個限制是不能指定格式字符串或分析標志。
有時,您需要在文本文檔(如 XML 文件或電子郵件)中包含二進制數(shù)據(jù)(如位圖)。Base 64 是一種普遍使用二進制數(shù)據(jù)編碼為可讀字符的方法,使用 ASCII 集中的 64 個字符。
Convert 的 ToBase64String 方法從字節(jié)數(shù)組轉(zhuǎn)換為基數(shù) 64;FromBase64String 執(zhí)行相反的操作。
如果要處理源自或發(fā)往 XML 文件的數(shù)據(jù),XmlConvert(在 System.Xml 命名空間中)提供了最合適的格式設(shè)置和分析方法。XmlConvert 中的方法處理 XML 格式的細微差別,而無需特殊的格式字符串。例如,XML 中的 true 是“true”而不是“True”。.NET BCL 在內(nèi)部廣泛使用 XmlConvert。XmlConvert 也適用于獨立于區(qū)域性的通用序列化。
XmlConvert 中的格式化方法都作為重載的 ToString 方法提供;解析方法稱為 ToBoolean 、ToDateTime 等:
string s=XmlConvert.ToString (true); // s="true"
bool isTrue=XmlConvert.ToBoolean (s);
與 DateTime 相互轉(zhuǎn)換的方法接受 XmlDateTimeSerializationMode 參數(shù)。這是一個具有以下值的枚舉:
Unspecified, Local, Utc, RoundtripKind
本地和 UTC 會導致在格式化時發(fā)生轉(zhuǎn)換(如果日期時間尚未在該時區(qū)中)。然后將時區(qū)追加到字符串中:
2010-02-22T14:08:30.9375 // Unspecified
2010-02-22T14:07:30.9375+09:00 // Local
2010-02-22T05:08:30.9375Z // Utc
未指定在格式化之前剝離嵌入在日期時間(即日期時間種類)中的任何時區(qū)信息。RoundtripKind 遵循 DateTime 的 DateTimeKind — 因此當它被重新分析時,生成的 DateTime 結(jié)構(gòu)將完全符合它原來的樣子。
類型轉(zhuǎn)換器設(shè)計用于在設(shè)計時環(huán)境中格式化和分析。它們還分析可擴展應(yīng)用程序標記語言 (XAML) 文檔中的值,如 Windows Presentation Foundation (WPF) 中使用的值。
在 .NET 中,有 100 多個類型轉(zhuǎn)換器,涵蓋顏色、圖像和 URI 等內(nèi)容。相比之下,格式提供程序只為少數(shù)簡單值類型實現(xiàn)。
類型轉(zhuǎn)換器通常以多種方式分析字符串,無需提示。例如,在 Visual Studio 的 WPF 應(yīng)用程序中,如果通過在相應(yīng)的屬性窗口中鍵入“Miige”為控件分配背景色,則 Color 的類型轉(zhuǎn)換器會確定您引用的是顏色名稱,而不是 RGB 字符串或系統(tǒng)顏色。這種靈活性有時可以使類型轉(zhuǎn)換器在設(shè)計器和 XAML 文檔之外的上下文中有用。
所有類型轉(zhuǎn)換器子類類型轉(zhuǎn)換器在系統(tǒng)組件模型中。要獲取 TypeConverter,請調(diào)用 TypeDescriptor.GetConverter 。下面獲取顏色類型的類型轉(zhuǎn)換器(在 System.Drawing 命名空間中):
TypeConverter cc=TypeDescriptor.GetConverter (typeof (Color));
在許多其他方法中,TypeConverter 定義了 ConvertToString 和 ConvertFromString 的方法。我們可以按如下方式調(diào)用這些:
Color beige=(Color) cc.ConvertFromString ("Beige");
Color purple=(Color) cc.ConvertFromString ("#800080");
Color window=(Color) cc.ConvertFromString ("Window");
按照約定,類型轉(zhuǎn)換器的名稱以 結(jié)尾,并且通常與它們要轉(zhuǎn)換的類型位于同一命名空間中。一個類型通過TypeConverterAttribute鏈接到它的轉(zhuǎn)換器,允許設(shè)計師自動拾取轉(zhuǎn)換器。
類型轉(zhuǎn)換器還可以提供設(shè)計時服務(wù),例如生成標準值列表以填充設(shè)計器中的下拉列表或協(xié)助代碼。
大多數(shù)基類型都可以通過調(diào)用 BitConverter 轉(zhuǎn)換為字節(jié)數(shù)組。獲取字節(jié) :
foreach (byte b in BitConverter.GetBytes (3.5))
Console.Write (b + " "); // 0 0 0 0 0 0 12 64
BitConverter還提供了方法,例如ToDouble,用于在另一個方向上進行轉(zhuǎn)換。
十進制和日期時間(偏移量)類型不受BitConverter的支持。但是,您可以通過調(diào)用十進制將小數(shù)轉(zhuǎn)換為 int 數(shù)組。獲取比特 .反過來說,decimal 提供了一個接受 int 數(shù)組的構(gòu)造函數(shù)。
在 DateTime 的情況下,您可以在實例上調(diào)用 ToBinary,這將返回一個 long(然后您可以使用 BitConverter)。靜態(tài) DateTime.FromBinary 方法執(zhí)行相反的操作。
應(yīng)用程序有兩個方面:和。
涉及三項任務(wù)(按重要性降序排列):
意味著通過為特定區(qū)域性編寫附屬程序集來完成最后一項任務(wù)。您可以在編寫程序執(zhí)行此操作(我們將在第 的中介紹詳細信息)。
.NET 通過默認應(yīng)用特定于區(qū)域性的規(guī)則來幫助你完成第二個任務(wù)。我們已經(jīng)了解了在日期時間或數(shù)字上調(diào)用 ToString 如何遵循本地格式規(guī)則。遺憾的是,這很容易使第一個任務(wù)失敗并導致程序中斷,因為您希望根據(jù)假定的區(qū)域性設(shè)置日期或數(shù)字的格式。正如我們所看到的,解決方案是在格式化和分析時指定區(qū)域性(例如固定區(qū)域性),或者使用與區(qū)域性無關(guān)的方法(例如 XmlConvert 中的方法)。
我們已經(jīng)在本章中介紹了要點。以下是所需基本工作的摘要:
您可以通過重新分配 Thread 的 CurrentCulture 屬性(在 System.Threading 中)來針對不同的區(qū)域性進行測試。以下內(nèi)容將當前區(qū)域性更改為:
Thread.CurrentThread.CurrentCulture=CultureInfo.GetCultureInfo ("tr-TR");
土耳其是一個特別好的測試案例,因為:
您還可以通過在 Windows 控制面板中更改數(shù)字和日期格式設(shè)置來進行試驗:這些設(shè)置反映在默認區(qū)域性 ( CultureInfo.CurrentCulture ) 中。
CultureInfo.GetCulture() 返回所有可用區(qū)域性的數(shù)組。
Thread 和 CultureInfo 還支持 CurrentUICulture 屬性。這更關(guān)注本地化,我們將在第中介紹。
我們在前面的章節(jié)和章節(jié)中介紹了數(shù)字轉(zhuǎn)換; 總結(jié)了所有選項。
數(shù)值轉(zhuǎn)換摘要 | ||
任務(wù) | 功能 | 例子 |
e | Parse TryParse | 國際 i;布爾確定=整數(shù)。TryParse (“3”, out i); |
從基數(shù) 2、8 或 16 解析 | Convert.toIntegral | int i=Convert.ToInt32 (“1E”, 16); |
格式化為十六進制 | ToString (“X”) | 字符串十六進制=45.ToString (“X”); |
無損數(shù)字轉(zhuǎn)換 | 隱式演員表 | 整數(shù) i=23;雙 d=i; |
數(shù)值轉(zhuǎn)換 | 顯式強制轉(zhuǎn)換 | 雙倍 d=23.5; int i=(int) d; |
數(shù)字轉(zhuǎn)換(實數(shù)到整數(shù)) | Convert.toIntegral | 雙倍 d=23.5; int i=Convert.ToInt32 (d); |
列出了靜態(tài) Math 類的關(guān)鍵成員。三角函數(shù)接受雙精度類型的參數(shù);其他方法(如 Max)被重載以對所有數(shù)值類型進行操作。數(shù)學類還定義了數(shù)學常數(shù) E() 和 PI。
靜態(tài)數(shù)學類中的方法 | |
類別 | 方法 |
舍入 | 圓形 , 截斷 , 地板 , 天花板 |
最大值/最小值 | 最大 , 最小值 |
絕對值和符號 | 腹肌 , 標志 |
平方根 | Sqrt |
提升到權(quán)力 | 戰(zhàn)俘 , 經(jīng)驗 |
對數(shù) | 日志 , 日志10 |
三角 | 罪 , 科斯 , 譚 , 辛 , 科什 , 坦 , 阿辛 , 阿科斯 , 阿坦 |
Round 方法允許您指定舍入的小數(shù)位數(shù)以及如何處理中點(遠離零或使用銀行家舍入)。樓層和天花板舍入到最接近的整數(shù):地板始終向下舍入,天花板始終向上舍入 - 即使使用負數(shù)也是如此。
Max 和 Min 只接受兩個論點。如果您有數(shù)字數(shù)組或序列,請使用 System.Linq.Enumerable 中的 Max 和 Min 擴展方法。
BigInteger 結(jié)構(gòu)是一種專用的數(shù)值類型。它駐留在 System.Numerics 命名空間中,允許您表示任意大的整數(shù),而不會損失任何精度。
C#不提供對BigInteger的原生支持,所以沒有辦法表示BigInteger文字。但是,您可以從任何其他整數(shù)類型隱式轉(zhuǎn)換為 BigInteger:
BigInteger twentyFive=25; // implicit conversion from integer
表示更大的數(shù)字,例如一個 googol (10100),您可以使用 BigInteger 的靜態(tài)方法之一,例如 Pow(提高到冪):
BigInteger googol=BigInteger.Pow (10, 100);
或者,您可以解析字符串:
BigInteger googol=BigInteger.Parse ("1".PadRight (101, '0'));
在此調(diào)用 ToString() 會打印每個數(shù)字:
Console.WriteLine (googol.ToString()); // 10000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000
可以使用顯式強制轉(zhuǎn)換運算符在 BigInteger 和標準數(shù)值類型之間執(zhí)行潛在的有損轉(zhuǎn)換:
double g2=(double) googol; // Explicit cast
BigInteger g3=(BigInteger) g2; // Explicit cast
Console.WriteLine (g3);
由此產(chǎn)生的輸出演示了精度的損失:
9999999999999999673361688041166912...
BigInteger重載所有算術(shù)運算符,包括余數(shù)(%)以及比較和相等運算符。
您還可以從字節(jié)數(shù)組構(gòu)造 BigInteger。以下代碼生成一個適合加密的 32 字節(jié)隨機數(shù),然后將其分配給 BigInteger:
// This uses the System.Security.Cryptography namespace:
RandomNumberGenerator rand=RandomNumberGenerator.Create();
byte[] bytes=new byte [32];
rand.GetBytes (bytes);
var bigRandomNumber=new BigInteger (bytes); // Convert to BigInteger
將這樣的數(shù)字存儲在 BigInteger 中而不是字節(jié)數(shù)組的優(yōu)點是可以獲得值類型語義。調(diào)用 ToByteArray 會將 BigInteger 轉(zhuǎn)換回字節(jié)數(shù)組。
Half 結(jié)構(gòu)是 16 位浮點類型,隨 .NET 5 一起引入。Half 主要用于與顯卡處理器互操作,在大多數(shù) CPU 中沒有本機支持。
您可以通過顯式強制轉(zhuǎn)換在 Half 和 float 或 double 之間進行轉(zhuǎn)換:
Half h=(Half) 123.456;
Console.WriteLine (h); // 123.44 (note loss of precision)
沒有為此類型定義算術(shù)運算,因此必須轉(zhuǎn)換為另一種類型(如浮點型或雙精度型)才能執(zhí)行計算。
一半的范圍為 -65500 到 65500:
Console.WriteLine (Half.MinValue); // -65500
Console.WriteLine (Half.MaxValue); // 65500
請注意最大范圍內(nèi)的精度損失:
Console.WriteLine ((Half)65500); // 65500
Console.WriteLine ((Half)65490); // 65500
Console.WriteLine ((Half)65480); // 65470
復(fù)數(shù)結(jié)構(gòu)是另一種專門的數(shù)值類型,表示具有雙精度類型的實部和虛部的復(fù)數(shù)。Complex 駐留在命名空間中(與 BigInteger 一起)。
要使用 Complex ,實例化結(jié)構(gòu),指定實值和虛值:
var c1=new Complex (2, 3.5);
var c2=new Complex (3, 0);
還有來自標準數(shù)值類型的隱式轉(zhuǎn)換。
復(fù)雜結(jié)構(gòu)公開實值和虛值以及相位和量級的屬性:
Console.WriteLine (c1.Real); // 2
Console.WriteLine (c1.Imaginary); // 3.5
Console.WriteLine (c1.Phase); // 1.05165021254837
Console.WriteLine (c1.Magnitude); // 4.03112887414927
您還可以通過指定幅度和相位來構(gòu)造復(fù)數(shù):
Complex c3=Complex.FromPolarCoordinates (1.3, 5);
標準算術(shù)運算符被重載以處理復(fù)數(shù):
Console.WriteLine (c1 + c2); // (5, 3.5)
Console.WriteLine (c1 * c2); // (6, 10.5)
Complex 結(jié)構(gòu)公開了更高級函數(shù)的靜態(tài)方法,包括:
Random 類生成隨機字節(jié) s、整數(shù) s 或雙精度 s 的偽隨機序列。
要使用 隨機 ,您首先實例化它,可以選擇提供一個種子來啟動隨機數(shù)序列。使用相同的種子可以保證相同的數(shù)字系列(如果在同一 CLR 版本下運行),這在需要時有時很有用:
Random r1=new Random (1);
Random r2=new Random (1);
Console.WriteLine (r1.Next (100) + ", " + r1.Next (100)); // 24, 11
Console.WriteLine (r2.Next (100) + ", " + r2.Next (100)); // 24, 11
如果你不想要可重復(fù)性,你可以在沒有種子的情況下構(gòu)建隨機;在這種情況下,它使用當前系統(tǒng)時間來彌補一個。
由于系統(tǒng)時鐘的粒度有限,因此兩個相鄰創(chuàng)建的隨機實例(通常在 10 毫秒內(nèi))將產(chǎn)生相同的值序列。一個常見的陷阱是每次需要隨機數(shù)時實例化一個新的 Random 對象,而不是重用對象。
一個好的模式是聲明單個靜態(tài)隨機實例。但是,在多線程方案中,這可能會導致麻煩,因為隨機對象不是線程安全的。我們在中描述了一種解決方法。
調(diào)用將生成一個介于 0 和 之間的隨機整數(shù)。NextDouble 生成一個介于 0 和 1 之間的隨機雙精度值。NextBytes 用隨機值填充字節(jié)數(shù)組。Next(n)n?1
對于高安全性應(yīng)用程序(如加密)而言,隨機性被認為不夠隨機。為此,.NET 在 System.Security.Cryptography 命名空間中提供了一個隨機數(shù)生成器。以下是使用它的方法:
var rand=System.Security.Cryptography.RandomNumberGenerator.Create();
byte[] bytes=new byte [32];
rand.GetBytes (bytes); // Fill the byte array with random numbers.
缺點是它不太靈活:填充字節(jié)數(shù)組是獲取隨機數(shù)的唯一方法。要獲取整數(shù),必須使用 BitConverter :
byte[] bytes=new byte [4];
rand.GetBytes (bytes);
int i=BitConverter.ToInt32 (bytes, 0);
System.Numerics.BitOperations 類(來自 .NET 6)公開了以下方法來幫助進行 base-2 操作:
IsPow2
如果數(shù)字是 2 的冪,則返回 true
LeadingZeroCount / TrailingZeroCount
返回格式為 base-2 32 位或 64 位無符號整數(shù)時前導零的數(shù)目
日志2
返回無符號整數(shù)的整數(shù) base-2 日志
流行計數(shù)
返回無符號整數(shù)中設(shè)置為 1 的位數(shù)
向左旋轉(zhuǎn)/向右旋轉(zhuǎn)
執(zhí)行按位左/右旋轉(zhuǎn)
RoundUpToPowerOf2
將無符號整數(shù)向上舍入到最接近的 2 的冪
在第 中,我們描述了 C# 的枚舉類型,并展示了如何組合成員、測試相等性、使用邏輯運算符和執(zhí)行轉(zhuǎn)換。.NET 通過 System.Enum 類型擴展了 C# 對枚舉的支持。此類型有兩個角色:
意味著您可以將任何枚舉成員隱式強制轉(zhuǎn)換為 System.Enum 實例:
Display (Nut.Macadamia); // Nut.Macadamia
Display (Size.Large); // Size.Large
void Display (Enum value)
{
Console.WriteLine (value.GetType().Name + "." + value.ToString());
}
enum Nut { Walnut, Hazelnut, Macadamia }
enum Size { Small, Medium, Large }
System.Enum 上的靜態(tài)實用程序方法主要與執(zhí)行轉(zhuǎn)換和獲取成員列表有關(guān)。
有三種方法可以表示枚舉值:
在本節(jié)中,我們將介紹如何在兩者之間進行轉(zhuǎn)換。
回想一下,顯式強制轉(zhuǎn)換在枚舉成員及其整數(shù)值之間進行轉(zhuǎn)換。如果您在編譯時知道枚舉類型,則顯式強制轉(zhuǎn)換是正確的方法:
[Flags]
public enum BorderSides { Left=1, Right=2, Top=4, Bottom=8 }
...
int i=(int) BorderSides.Top; // i==4
BorderSides side=(BorderSides) i; // side==BorderSides.Top
您可以以相同的方式將 System.Enum 實例強制轉(zhuǎn)換為其整型。訣竅是先強制轉(zhuǎn)換為對象,然后轉(zhuǎn)換整型:
static int GetIntegralValue (Enum anyEnum)
{
return (int) (object) anyEnum;
}
這取決于您知道整型類型:我們剛剛編寫的方法如果傳遞一個整型很長的枚舉就會崩潰。若要編寫適用于任何整型枚舉的方法,可以采用以下三種方法之一。第一個是調(diào)用 Convert.ToDecimal :
static decimal GetAnyIntegralValue (Enum anyEnum)
{
return Convert.ToDecimal (anyEnum);
}
這是有效的,因為每個整數(shù)類型(包括 ulong )都可以轉(zhuǎn)換為十進制而不會丟失信息。第二種方法是調(diào)用 Enum.GetUnderlyingType 以獲取枚舉的整型,然后調(diào)用 Convert.ChangeType:
static object GetBoxedIntegralValue (Enum anyEnum)
{
Type integralType=Enum.GetUnderlyingType (anyEnum.GetType());
return Convert.ChangeType (anyEnum, integralType);
}
這將保留原始整型,如以下示例所示:
object result=GetBoxedIntegralValue (BorderSides.Top);
Console.WriteLine (result); // 4
Console.WriteLine (result.GetType()); // System.Int32
我們的 GetBoxedIntegralType 方法實際上不執(zhí)行任何值轉(zhuǎn)換;相反,它會在另一種類型中相同的值。它將型服裝中的積分值轉(zhuǎn)換為服裝中的積分值。我們將在中進一步描述這一點。
第三種方法是調(diào)用 Format 或 ToString,指定“d”或“D”格式字符串。這為您提供了 enum 的整數(shù)值作為字符串,并且在編寫自定義序列化格式化程序時很有用:
static string GetIntegralValueAsString (Enum anyEnum)
{
return anyEnum.ToString ("D"); // returns something like "4"
}
Enum.ToObject 將整數(shù)值轉(zhuǎn)換為給定類型的枚舉實例:
object bs=Enum.ToObject (typeof (BorderSides), 3);
Console.WriteLine (bs); // Left, Right
這是以下內(nèi)容的動態(tài)等效項:
BorderSides bs=(BorderSides) 3;
ToObject 被重載以接受所有整型以及對象。(后者適用于任何盒裝積分類型。
若要將枚舉轉(zhuǎn)換為字符串,可以調(diào)用靜態(tài) Enum.Format 方法,也可以在實例上調(diào)用 ToString。每個方法都接受一個格式字符串,該字符串可以是“G”表示默認格式設(shè)置行為,“D”表示以字符串形式發(fā)出基礎(chǔ)整數(shù)值,“X”表示十六進制格式相同的字符串,或者“F”表示格式化沒有 Flags 屬性的枚舉的組合成員。我們在中列出了這些示例。
Enum.Parse 將字符串轉(zhuǎn)換為枚舉。它接受枚舉類型和可以包含多個成員的字符串:
BorderSides leftRight=(BorderSides) Enum.Parse (typeof (BorderSides),
"Left, Right");
可選的第三個參數(shù)允許您執(zhí)行不區(qū)分大小寫的分析。如果未找到該成員,則會引發(fā) ArgumentException。
Enum.GetValues 返回一個包含特定枚舉類型的所有成員的數(shù)組:
foreach (Enum value in Enum.GetValues (typeof (BorderSides)))
Console.WriteLine (value);
復(fù)合成員,例如 左右=左 |權(quán)利也包括在內(nèi)。
Enum.GetNames 執(zhí)行相同的函數(shù),但返回一個數(shù)組。
在內(nèi)部,CLR 通過反映枚舉類型中的字段來實現(xiàn) GetValues 和 GetNames。緩存結(jié)果以提高效率。
枚舉的語義主要由編譯器強制執(zhí)行。在 CLR 中,枚舉實例(未裝箱時)與其基礎(chǔ)整數(shù)值之間沒有運行時差異。此外,CLR 中的枚舉定義只是 System.Enum 的一個子類型,每個成員都有靜態(tài)積分類型字段。這使得枚舉的常規(guī)使用非常高效,運行時成本與整數(shù)的成本相匹配。
這種策略的缺點是枚舉可以提供但不的類型安全性。我們在中看到了一個例子:
[Flags] public enum BorderSides { Left=1, Right=2, Top=4, Bottom=8 }
...
BorderSides b=BorderSides.Left;
b +=1234; // No error!
當編譯器無法執(zhí)行驗證時(如本例所示),運行時沒有備份來引發(fā)異常。
我們所說的枚舉實例與其整數(shù)值之間沒有運行時差異的內(nèi)容似乎與以下內(nèi)容不一致:
[Flags] public enum BorderSides { Left=1, Right=2, Top=4, Bottom=8 }
...
Console.WriteLine (BorderSides.Right.ToString()); // Right
Console.WriteLine (BorderSides.Right.GetType().Name); // BorderSides
鑒于運行時枚舉實例的性質(zhì),您會期望它打印 2 和 Int32 !其行為的原因歸結(jié)為一些更多的編譯時技巧。C# 在調(diào)用枚舉實例的虛擬方法(如 ToString 或 GetType)之前顯式枚舉實例。當枚舉實例被裝箱時,它會獲得引用其枚舉類型的運行時包裝。
Guid 結(jié)構(gòu)表示全局唯一標識符:一個 16 字節(jié)的值,在生成時,幾乎可以肯定在世界上是唯一的。Guid 通常用于應(yīng)用程序和數(shù)據(jù)庫中的各種鍵。有 2 個128或 3.4 × 1038獨特的吉德 .
靜態(tài) Guid.NewGuid 方法生成一個唯一的 Guid:
Guid g=Guid.NewGuid ();
Console.WriteLine (g.ToString()); // 0d57629c-7d6e-4847-97cb-9e2fc25083fe
若要實例化現(xiàn)有值,請使用其中一個構(gòu)造函數(shù)。兩個最有用的構(gòu)造函數(shù)是:
public Guid (byte[] b); // Accepts a 16-byte array
public Guid (string g); // Accepts a formatted string
表示為字符串時,Guid 的格式為 32 位十六進制數(shù)字,第 8、12、16 和 20 位數(shù)字后帶有可選的連字符。整個字符串也可以選擇用括號或大括號括起來:
Guid g1=new Guid ("{0d57629c-7d6e-4847-97cb-9e2fc25083fe}");
Guid g2=new Guid ("0d57629c7d6e484797cb9e2fc25083fe");
Console.WriteLine (g1==g2); // True
作為結(jié)構(gòu)體,Guid 尊重值類型語義;因此,相等運算符在前面的示例中起作用。
ToByteArray 方法將 Guid 轉(zhuǎn)換為字節(jié)數(shù)組。
靜態(tài) Guid.Empty 屬性返回一個空 Guid(全部為零)。這通常用于代替 null 。
到目前為止,我們假設(shè)==和 !=運算符是相等比較的全部內(nèi)容。然而,平等問題更加復(fù)雜和微妙,有時需要使用額外的方法和接口。本節(jié)探討了相等的標準 C# 和 .NET 協(xié)議,特別關(guān)注兩個:
但在探索平等協(xié)議的細節(jié)以及如何定制它們之前,我們首先必須看看價值與參考平等的初步概念。
有兩種平等:
價值平等
從某種意義上說,兩個值是的。
參照相等
兩個引用引用完全相同。
除非被覆蓋:
實際上,值類型使用值相等(除非裝箱)。值相等的簡單演示是比較兩個數(shù)字:
int x=5, y=5;
Console.WriteLine (x==y); // True (by virtue of value equality)
一個更詳細的演示是比較兩個 DateTimeOffset 結(jié)構(gòu)。以下打印 True,因為兩個 DateTimeOffset 引用,因此被視為等效:
var dt1=new DateTimeOffset (2010, 1, 1, 1, 1, 1, TimeSpan.FromHours(8));
var dt2=new DateTimeOffset (2010, 1, 1, 2, 1, 1, TimeSpan.FromHours(9));
Console.WriteLine (dt1==dt2); // True
DateTimeOffset 是一個結(jié)構(gòu),其相等語義已得到調(diào)整。默認情況下,結(jié)構(gòu)表現(xiàn)出一種稱為結(jié)構(gòu)相等的特殊值相等,其中兩個值被視為相等,如果它們的所有成員都。(您可以通過創(chuàng)建一個結(jié)構(gòu)并調(diào)用其 Equals 方法來查看這一點;稍后會詳細介紹。
默認情況下,引用類型顯示引用相等性。在下面的示例中,f1 和 f2 不相等,盡管它們的對象具有相同的內(nèi)容:
class Foo { public int X; }
...
Foo f1=new Foo { X=5 };
Foo f2=new Foo { X=5 };
Console.WriteLine (f1==f2); // False
相反,f3 和 f1 相等,因為它們引用同一個對象:
Foo f3=f1;
Console.WriteLine (f1==f3); // True
在本節(jié)的后面部分,我們將介紹如何引用類型以顯示值相等性。這方面的一個例子是系統(tǒng)命名空間中的 Uri 類:
Uri uri1=new Uri ("http://www.linqpad.net");
Uri uri2=new Uri ("http://www.linqpad.net");
Console.WriteLine (uri1==uri2); // True
字符串類表現(xiàn)出類似的行為:
var s1="http://www.linqpad.net";
var s2="http://" + "www.linqpad.net";
Console.WriteLine (s1==s2); // True
類型可以實現(xiàn)三種標準協(xié)議以進行相等:
此外,還有協(xié)議和 IStructuralEequalable 接口,我們將在第 中描述。
我們已經(jīng)在許多示例中看到了標準==和 !=運算符如何執(zhí)行相等/不等式比較。==和 !=的微妙之處之所以出現(xiàn),是因為它們是;因此,它們是靜態(tài)解析的(實際上,它們是作為靜態(tài)函數(shù)實現(xiàn)的)。因此,當您使用==或 !=時,C# 會根據(jù)決定哪種類型將執(zhí)行比較,并且不會發(fā)生任何虛擬行為。這通常是可取的。在下面的示例中,編譯器將==硬連線到 int 類型,因為 x 和 y 都是 int :
int x=5;
int y=5;
Console.WriteLine (x==y); // True
但在下一個示例中,編譯器將==運算符連接到對象類型:
object x=5;
object y=5;
Console.WriteLine (x==y); // False
因為 object 是一個類(因此是一個引用類型),對象的==運算符使用來比較 x 和 y。結(jié)果為 false,因為 x 和 y 分別引用堆上的不同盒裝對象。
為了在前面的示例中正確等同 x 和 y,我們可以使用虛擬 Equals 方法。等于在 System.Object 中定義,因此適用于所有類型:
object x=5;
object y=5;
Console.WriteLine (x.Equals (y)); // True
等于在運行時根據(jù)對象的實際類型進行解析。在這種情況下,它調(diào)用 Int32 的 Equals 方法,該方法將應(yīng)用于操作數(shù),返回 true 。對于引用類型,Equals 默認執(zhí)行引用相等比較;對于結(jié)構(gòu),Equals 通過對其每個字段調(diào)用 Equals 來執(zhí)行結(jié)構(gòu)比較。
您可能想知道為什么 C# 的設(shè)計者沒有通過使==虛擬從而在功能上與 Equals 相同來避免這個問題。原因有三:
從本質(zhì)上講,設(shè)計的復(fù)雜性反映了情況的復(fù)雜性:平等的概念涵蓋了多種場景。
因此,Equals 適合以與類型無關(guān)的方式等同兩個對象。以下方法等同于任何類型的兩個對象:
public static bool AreEqual (object obj1, object obj2)=> obj1.Equals (obj2);
但是,在一種情況下,此操作會失敗。如果第一個參數(shù)為 null ,則得到一個 NullReferenceException 。這是修復(fù):
public static bool AreEqual (object obj1, object obj2)
{
if (obj1==null) return obj2==null;
return obj1.Equals (obj2);
}
或者,更簡潔地說:
public static bool AreEqual (object obj1, object obj2)=> obj1==null ? obj2==null : obj1.Equals (obj2);
對象類提供了一個靜態(tài)幫助程序方法,該方法執(zhí)行前面示例中 AreEqual 的工作。它的名稱是 Equals(就像虛擬方法一樣),但沒有沖突,因為它接受參數(shù):
public static bool Equals (object objA, object objB)
這為編譯時類型未知的情況提供了一種 null 安全的相等比較算法:
object x=3, y=3;
Console.WriteLine (object.Equals (x, y)); // True
x=null;
Console.WriteLine (object.Equals (x, y)); // False
y=null;
Console.WriteLine (object.Equals (x, y)); // True
一個有用的應(yīng)用程序是在編寫泛型類型時。如果對象,則以下代碼將無法編譯。等于替換為==或 !=運算符:
class Test <T>
{
T _value;
public void SetValue (T newValue)
{
if (!object.Equals (newValue, _value))
{
_value=newValue;
OnValueChanged();
}
}
protected virtual void OnValueChanged() { ... }
}
此處禁止使用運算符,因為編譯器無法綁定到未知類型的靜態(tài)方法。
實現(xiàn)此比較的更精細方法是使用 EqualityComparer<T> 類。這樣做的好處是可以防止拳擊:
if (!EqualityComparer<T>.Default.Equals (newValue, _value))
我們將在第更詳細地討論EqualityComparer<T>(參見)。
有時,您需要強制引用相等比較。靜態(tài)對象。ReferenceEquals方法就是這樣做的:
Widget w1=new Widget();
Widget w2=new Widget();
Console.WriteLine (object.ReferenceEquals (w1, w2)); // False
class Widget { ... }
您可能希望這樣做,因為 Widget 可以覆蓋虛擬 Equals 方法,例如 w1。等于 (w2) 將返回 true 。此外,Widget 可能會重載==運算符,以便 w1==w2 也會返回 true。在這種情況下,調(diào)用對象。引用等于保證正常的引用相等語義。
強制引用相等比較的另一種方法是將值強制轉(zhuǎn)換為對象,然后應(yīng)用==運算符。
調(diào)用對象的結(jié)果。等于是它強制對值類型進行裝箱。這在對性能高度敏感的方案中是不可取的,因為與實際比較相比,裝箱相對昂貴。在 C# 2.0 中引入了一個解決方案,其中包含 IEquatable<T> 接口:
public interface IEquatable<T>
{
bool Equals (T other);
}
這個想法是IEquatable<T>在實現(xiàn)時,給出的結(jié)果與調(diào)用對象的虛擬Equals方法相同,但更快。大多數(shù)基本的.NET類型實現(xiàn)IEquatable<T>。您可以使用 IEquatable<T> 作為泛型類型的約束:
class Test<T> where T : IEquatable<T>
{
public bool IsEqual (T a, T b)
{
return a.Equals (b); // No boxing with generic T
}
}
如果我們刪除泛型約束,類仍將編譯,但 a.Equals(b) 將綁定到較慢的對象。等于(假設(shè) T 是值類型,則速度較慢)。
我們之前說過,有時==和 Equals 應(yīng)用不同的相等定義很有用。例如:
double x=double.NaN;
Console.WriteLine (x==x); // False
Console.WriteLine (x.Equals (x)); // True
雙精度類型的==運算符強制要求一個 NaN 永遠不能等于其他任何東西——即使是另一個 NaN。從數(shù)學角度來看,這是最自然的,它反映了底層 CPU 行為。然而,平等方法必須應(yīng)用平等;換句話說:
x.等于 (x) 必須返回 true。
集合和字典依賴于 Equals 以這種方式行事;否則,他們找不到以前存儲的項目。
對于值類型來說,讓 Equals 和==應(yīng)用不同的相等定義實際上非常罕見。更常見的方案是引用類型;當作者自定義 Equals 以便它執(zhí)行值相等,同時讓==執(zhí)行(默認)參照相等時,就會發(fā)生這種情況。StringBuilder 類正是這樣做的:
var sb1=new StringBuilder ("foo");
var sb2=new StringBuilder ("foo");
Console.WriteLine (sb1==sb2); // False (referential equality)
Console.WriteLine (sb1.Equals (sb2)); // True (value equality)
現(xiàn)在讓我們看看如何自定義相等性。
回想一下默認的相等比較行為:
進一步:
有時,在編寫類型時重寫此行為是有意義的。這樣做有兩種情況:
當==和 Equals 的默認行為對您的類型不自然且時,更改相等的含義是有意義的。一個例子是 DateTimeOffset ,一個具有兩個私有字段的結(jié)構(gòu):UTC DateTime 和一個數(shù)字整數(shù)偏移量。如果要編寫此類型,則可能需要確保相等比較僅考慮 UTC 日期時間字段,而不考慮偏移量字段。另一個示例是支持 NaN 值的數(shù)字類型,例如浮點數(shù)和雙精度數(shù)。如果您自己實現(xiàn)此類類型,則需要確保在相等比較中支持 NaN 比較邏輯。
對于類,有時更自然地將相等作為默認值,而不是。對于保存簡單數(shù)據(jù)段的小型類,例如System.Uri(或System.String),通常就是這種情況。
對于記錄,編譯器會自動實現(xiàn)結(jié)構(gòu)相等(通過比較每個字段)。但是,有時這將包括您不想比較的字段,或需要特殊比較邏輯的對象,例如集合。使用記錄覆蓋相等的過程略有不同,因為記錄遵循一種特殊的模式,該模式旨在很好地配合其繼承規(guī)則。
結(jié)構(gòu)的默認比較算法相對較慢。通過覆蓋 Equals 來接管此過程可以將性能提高五倍。重載==運算符并實現(xiàn) IEquatable<T> 允許無框相等比較,這可以再次將事情加快五倍。
重寫引用類型的相等語義不會影響性能。引用相等比較的默認算法已經(jīng)非常快,因為它只是比較兩個 32 位或 64 位引用。
自定義相等還有另一種相當奇特的情況,那就是改進結(jié)構(gòu)的哈希算法以獲得更好的哈希表中的性能。這是因為相等比較和哈希在臀部連接在一起。我們稍后會檢查哈希。
要覆蓋類或結(jié)構(gòu)的相等性,請執(zhí)行以下步驟:
該過程與記錄不同(且更簡單),因為編譯器已經(jīng)根據(jù)自己的特殊模式覆蓋了相等方法和運算符。如果要進行干預(yù),則必須符合此模式,這意味著使用如下所示的簽名編寫 Equals 方法:
record Test (int X, int Y)
{
public virtual bool Equals (Test t)=> t !=null && t.X==X && t.Y==Y;
}
請注意,Equals 是虛擬的(不是覆蓋的),并接受實際的記錄類型(在本例中為 test,而不是對象)。編譯器將識別您的方法具有“正確”的簽名,并將其修補。
您還必須覆蓋 GetHashCode() ,就像使用類或結(jié)構(gòu)一樣。你不需要(也不應(yīng)該)重載!=和==,或者實現(xiàn)IEquatable<T>,因為這已經(jīng)為你完成了。
System.Object - 其成員占用空間很小 - 定義了一個具有專門和狹隘目的的方法,這似乎很奇怪。GetHashCode 是 Object 中的一個虛擬方法,符合此描述;它的存在主要是為了滿足以下兩種類型:
System.Collections.Hashtable
System.Collections.Generic.Dictionary<TKey,TValue>
這些是 - 每個元素都有一個用于存儲和檢索的鍵的集合。哈希表應(yīng)用非常具體的策略,根據(jù)元素的鍵有效地分配元素。這要求每個密鑰都有一個 Int32 編號或。每個鍵的哈希代碼不必是唯一的,但應(yīng)盡可能多樣,以獲得良好的哈希表性能。哈希表被認為足夠重要,因此 GetHashCode 在 System.Object 中定義,因此每種類型都可以發(fā)出哈希代碼。
我們將在第中詳細描述哈希表。
引用和值類型都有 GetHashCode 的默認實現(xiàn),這意味著您不需要覆蓋此方法—— Equals。(如果你覆蓋GetHashCode,你幾乎肯定會希望也覆蓋Equals。
以下是覆蓋對象的其他規(guī)則。獲取哈希碼 :
為了在哈希表中獲得最佳性能,您應(yīng)該編寫 GetHashCode,以便最大程度地減少兩個不同值返回相同哈希代碼的可能性。這就產(chǎn)生了在結(jié)構(gòu)上覆蓋 Equals 和 GetHashCode 的第三個原因,即提供比默認值更有效的哈希算法。結(jié)構(gòu)的默認實現(xiàn)由運行時自行決定,并且可以基于結(jié)構(gòu)中的每個字段。
相比之下,的默認 GetHashCode 實現(xiàn)基于對象令牌,該令牌對于 CLR 當前中的每個實例都是唯一的。
如果對象的哈希代碼在作為鍵添加到字典后發(fā)生更改,則該對象將無法再在字典中訪問。您可以通過基于不可變字段進行哈希代碼計算來搶占這種情況。
我們提供了一個完整的示例,說明如何很快覆蓋GetHashCode。
對象的公理。等于如下:
除了覆蓋 Equals 之外,您還可以選擇重載相等和不相等運算符。這幾乎總是通過結(jié)構(gòu)完成的,因為不這樣做的后果是==和 !=運算符根本不適用于您的類型。
對于類,有兩種方法可以繼續(xù):
第一種方法在自定義類型(尤其是類型)中最常見。它確保您的類型遵循==和 !=應(yīng)與引用類型表現(xiàn)出引用相等的期望,這可以防止混淆使用者。我們之前看到過一個例子:
var sb1=new StringBuilder ("foo");
var sb2=new StringBuilder ("foo");
Console.WriteLine (sb1==sb2); // False (referential equality)
Console.WriteLine (sb1.Equals (sb2)); // True (value equality)
第二種方法對于使用者永遠不需要引用相等的類型是有意義的。這些通常是不可變的(例如字符串和 System.Uri 類),有時是結(jié)構(gòu)體的良好候選項。
雖然有可能重載 !=這樣它的意思不是!(==) ,這在實踐中幾乎從未做過,除了比較浮點數(shù)等情況。南。
為了完整起見,在覆蓋等于時實現(xiàn) IEquatable<T> 也很好。其結(jié)果應(yīng)始終與重寫對象的 Equals 方法的結(jié)果匹配。如果按照稍后的示例構(gòu)建 Equals 方法實現(xiàn),則實現(xiàn) IEquatable<T> 無需編程成本。
想象一下,我們需要一個結(jié)構(gòu)來表示寬度和高度可互換的區(qū)域。換句話說,5 × 10 等于 10 × 5。(這種類型適用于排列矩形形狀的算法。
下面是完整的代碼:
public struct Area : IEquatable <Area>
{
public readonly int Measure1;
public readonly int Measure2;
public Area (int m1, int m2)
{
Measure1=Math.Min (m1, m2);
Measure2=Math.Max (m1, m2);
}
public override bool Equals (object other)=> other is Area a && Equals (a); // Calls method below
public bool Equals (Area other) // Implements IEquatable<Area>=> Measure1==other.Measure1 && Measure2==other.Measure2;
public override int GetHashCode()=> HashCode.Combine (Measure1, Measure2);
public static bool operator==(Area a1, Area a2)=> a1.Equals (a2);
public static bool operator !=(Area a1, Area a2)=> !a1.Equals (a2);
}
從 C# 10 開始,可以使用記錄縮短該過程。通過將其聲明為 記錄結(jié)構(gòu) ,您可以刪除構(gòu)造函數(shù)后面的所有代碼。
在實現(xiàn) GetHashCode 時,我們使用了 。NET的HashCode.Combation函數(shù)來生成復(fù)合哈希碼。(在該函數(shù)存在之前,一種流行的方法是將每個值乘以某個素數(shù),然后將它們相加。
下面是 Area 結(jié)構(gòu)的演示:
Area a1=new Area (5, 10);
Area a2=new Area (10, 5);
Console.WriteLine (a1.Equals (a2)); // True
Console.WriteLine (a1==a2); // True
如果希望類型僅針對特定方案采用不同的相等語義,則可以使用可插入的 IEqualityComparer 。這與標準集合類結(jié)合使用時特別有用,我們將在下一章中對其進行描述。
除了定義相等的標準協(xié)議外,C# 和 .NET 還定義了兩個標準協(xié)議,用于確定一個對象相對于另一個對象的順序:
IComparable 接口由通用排序算法使用。在下面的示例中,靜態(tài) Array.Sort 方法之所以有效,是因為 System.String 實現(xiàn)了 IComparable 接口:
string[] colors={ "Green", "Red", "Blue" };
Array.Sort (colors);
foreach (string c in colors) Console.Write (c + " "); // Blue Green Red
<運算符和>運算符更專用,它們主要用于數(shù)值類型。因為它們是靜態(tài)解析的,所以它們可以轉(zhuǎn)換為高效的字節(jié)碼,適用于計算密集型算法。
.NET 還通過 IComparer 接口提供可插入的排序協(xié)議。我們將在第的最后一節(jié)中描述這些內(nèi)容。
可比較接口定義如下:
public interface IComparable { int CompareTo (object other); }
public interface IComparable<in T> { int CompareTo (T other); }
這兩個接口表示相同的功能。對于值類型,泛型類型安全接口比非泛型接口更快。在這兩種情況下,CompareTo 方法的工作方式如下:
例如:
Console.WriteLine ("Beck".CompareTo ("Anne")); // 1
Console.WriteLine ("Beck".CompareTo ("Beck")); // 0
Console.WriteLine ("Beck".CompareTo ("Chris")); // -1
大多數(shù)基本類型都實現(xiàn)了這兩個可比較接口。這些接口有時也會在編寫自定義類型時實現(xiàn)。我們稍后提供一個例子。
考慮一種既覆蓋等于又實現(xiàn) IComparable 接口的類型。你會期望當 Equals 返回 true 時,CompareTo 應(yīng)該返回 0 。你是對的。但這里有一個問題:
當 Equals 返回 false 時,CompareTo 可以返回它喜歡的內(nèi)容(只要它內(nèi)部一致)!
換句話說,相等可以比比較“挑剔”,但反之則不然(違反這一點,排序算法就會中斷)。因此,CompareTo可以說,“所有對象都是平等的”,而Equals則說,“但有些對象比其他對象更平等!
一個很好的例子是 System.String 。字符串的 Equals 方法和==運算符使用號比較,它比較每個字符的 Unicode 點值。然而,它的CompareTo方法使用了一種不太挑剔的文化性比較。例如,在大多數(shù)計算機上,字符串“?”和“ǖ”根據(jù)等于不同,但根據(jù)比較相同。
在第 中,我們討論了可插拔排序協(xié)議 IComparer,它允許您在排序或?qū)嵗判蚣蠒r指定替代排序算法。自定義 IComparer 可以進一步擴展 CompareTo 和 Equals 之間的差距 — 例如,不區(qū)分大小寫的字符串比較器在比較“A”和“a”時將返回 0。然而,反向規(guī)則仍然適用:CompareTo 永遠不能比 Equals 更挑剔。
在自定義類型中實現(xiàn) IComparable 接口時,可以通過編寫 CompareTo 的第一行來避免與此規(guī)則沖突,如下所示:
if (Equals (other)) return 0;
之后,它可以返回它喜歡的東西,只要它是一致的!
某些類型定義<運算符和>運算符。例如:
bool after2010=DateTime.Now > new DateTime (2010, 1, 1);
您可以期望<運算符和>運算符在實現(xiàn)時與 IComparable 接口在功能上保持一致。這是跨 .NET 的標準做法。
在<和>過載時實現(xiàn) IComparable 接口也是標準做法,但反之則不然。事實上,大多數(shù)實現(xiàn) IComparable 的 .NET 類型重載 < 和 > 。這與相等的情況不同,在覆蓋等于時重載==是正常的。
通常,只有在以下情況下,>和<才會重載:
System.String 不滿足最后一點:字符串比較的結(jié)果可能因語言而異。因此,字符串不支持>和<運算符:
bool error="Beck" > "Anne"; // Compile-time error
在以下表示音符的結(jié)構(gòu)中,我們實現(xiàn)了 IComparable 接口以及重載<和>運算符。為了完整起見,我們還覆蓋了 Equals / GetHashCode 并重載==和 !=:
public struct Note : IComparable<Note>, IEquatable<Note>, IComparable
{
int _semitonesFromA;
public int SemitonesFromA { get { return _semitonesFromA; } }
public Note (int semitonesFromA)
{
_semitonesFromA=semitonesFromA;
}
public int CompareTo (Note other) // Generic IComparable<T>
{
if (Equals (other)) return 0; // Fail-safe check
return _semitonesFromA.CompareTo (other._semitonesFromA);
}
int IComparable.CompareTo (object other) // Nongeneric IComparable
{
if (!(other is Note))
throw new InvalidOperationException ("CompareTo: Not a note");
return CompareTo ((Note) other);
}
public static bool operator < (Note n1, Note n2)=> n1.CompareTo (n2) < 0;
public static bool operator > (Note n1, Note n2)=> n1.CompareTo (n2) > 0;
public bool Equals (Note other) // for IEquatable<Note>=> _semitonesFromA==other._semitonesFromA;
public override bool Equals (object other)
{
if (!(other is Note)) return false;
return Equals ((Note) other);
}
public override int GetHashCode()=> _semitonesFromA.GetHashCode();
public static bool operator==(Note n1, Note n2)=> n1.Equals (n2);
public static bool operator !=(Note n1, Note n2)=> !(n1==n2);
}
靜態(tài)控制臺類處理基于控制臺的應(yīng)用程序的標準輸入/輸出。在命令行(控制臺)應(yīng)用程序中,輸入通過 讀取 、 讀取鍵 和 讀取線 來自鍵盤,輸出通過寫入和寫入線進入文本窗口。您可以使用屬性控制窗口的位置和尺寸 窗口左 、 窗口頂部 、 窗口高度 和 窗口寬度 。還可以更改“背景色”和“前景色”屬性,并使用“光標左”、“光標頂部”和“光標大小”屬性操作光標:
Console.WindowWidth=Console.LargestWindowWidth;
Console.ForegroundColor=ConsoleColor.Green;
Console.Write ("test... 50%");
Console.CursorLeft -=3;
Console.Write ("90%"); // test... 90%
Write 和 WriteLine 方法被重載以接受復(fù)合格式字符串(請參閱中的 String.Format)。但是,這兩種方法都不接受格式提供程序,因此您只能使用CultureInfo.CurrentCulture。(當然,解決方法是顯式調(diào)用字符串。格式 .)
屬性返回一個 TextWriter 。將 Console.Out 傳遞給需要 TextWriter 的方法是一種有用的方法,可以讓該方法寫入控制臺以進行診斷。
您還可以通過 SetIn 和 SetOut 方法重定向控制臺的輸入和輸出流:
// First save existing output writer:
System.IO.TextWriter oldOut=Console.Out;
// Redirect the console's output to a file:
using (System.IO.TextWriter w=System.IO.File.CreateText
("e:\\output.txt"))
{
Console.SetOut (w);
Console.WriteLine ("Hello world");
}
// Restore standard console output
Console.SetOut (oldOut);
在第 中,我們描述了流和文本編寫器的工作原理。
在Visual Studio下運行WPF或Windows Forms應(yīng)用程序時,控制臺的輸出會自動重定向到Visual Studio的輸出窗口(在調(diào)試模式下)。這可以使 Console.Write 用于診斷目的;盡管在大多數(shù)情況下,System.Diagnostics 命名空間中的 Debug 和 Trace 類更合適(請參閱)。
靜態(tài) System.Environment 類提供了一系列有用的屬性:
文件和文件夾
當前目錄 , 系統(tǒng)目錄 , 命令行
計算機和操作系統(tǒng)
機器名稱 , 處理器計數(shù) , OSVersion , 換行符
用戶登錄
用戶名 , 用戶交互 , 用戶域名
診斷
TickCount , StackTrace , WorkingSet , Version
您可以通過調(diào)用 獲取文件夾路徑 ;我們將在第 的中對此進行描述。
您可以使用以下三種方法訪問操作系統(tǒng)環(huán)境變量(在命令提示符下鍵入“set”時看到的內(nèi)容):GetEnvironmentVariable 、GetEnvironmentVariables 和 SetEnvironmentVariable 。
ExitCode 屬性允許您設(shè)置返回代碼(當從命令或批處理文件調(diào)用程序時),并且 FailFast 方法立即終止程序,而不執(zhí)行清理。
可用于 Windows 應(yīng)用商店應(yīng)用的環(huán)境類僅提供有限數(shù)量的成員(處理器計數(shù)、換行符和快速故障)。
System.Diagnostics中的進程類允許您啟動新進程。(在第中,我們描述了如何使用它與計算機上運行的其他進程進行交互)。
出于安全原因,Process 類不適用于 Windows 應(yīng)用商店應(yīng)用,并且不能啟動任意進程。相反,您必須使用 Windows.System.Launcher 類來“啟動”您有權(quán)訪問的 URI 或文件。例如:
Launcher.LaunchUriAsync (new Uri ("http://albahari.com"));
var file=await KnownFolders.DocumentsLibrary
.GetFileAsync ("foo.txt");
Launcher.LaunchFileAsync (file);
這將使用與 URI 方案或文件擴展名關(guān)聯(lián)的任何程序打開 URI 或文件。您的程序必須位于前臺才能正常工作。
靜態(tài) Process.Start 方法有幾個重載;最簡單的接受帶有可選參數(shù)的簡單文件名:
Process.Start ("notepad.exe");
Process.Start ("notepad.exe", "e:\\file.txt");
最靈活的重載接受 ProcessStartInfo 實例。有了這個,您可以捕獲和重定向啟動進程的輸入、輸出和錯誤輸出(如果您將 UseShellExecute 保留為 false)。下面捕獲調(diào)用 ipconfig 的輸出:
ProcessStartInfo psi=new ProcessStartInfo
{
FileName="cmd.exe",
Arguments="/c ipconfig /all",
RedirectStandardOutput=true,
UseShellExecute=false
};
Process p=Process.Start (psi);
string result=p.StandardOutput.ReadToEnd();
Console.WriteLine (result);
如果不重定向輸出,Process.Start 將并行執(zhí)行程序到調(diào)用方。如果要等待新進程完成,可以在進程對象上調(diào)用 WaitForExit,并提供可選的超時。
使用 UseShellExecute false(.NET 中的默認值),可以捕獲標準輸入、輸出和錯誤流,然后通過 StandardInput 、StandardOutput 和 StandardError 屬性寫入/讀取這些流。
當您需要重定向標準輸出和標準錯誤流時,會出現(xiàn)一個困難,因為您通常無法知道從每個錯誤流中讀取數(shù)據(jù)的順序(因為您事先不知道數(shù)據(jù)將如何交錯)。解決方案是一次從兩個流中讀取,您可以通過異步讀取(至少)其中一個流具體操作方法如下:
以下方法在捕獲輸出流和錯誤流的同時運行可執(zhí)行文件:
(string output, string errors) Run (string exePath, string args="")
{
using var p=Process.Start (new ProcessStartInfo (exePath, args)
{
RedirectStandardOutput=true,
RedirectStandardError=true,
UseShellExecute=false,
});
var errors=new StringBuilder ();
// Read from the error stream asynchronously...
p.ErrorDataReceived +=(sender, errorArgs)=>
{
if (errorArgs.Data !=null) errors.AppendLine (errorArgs.Data);
};
p.BeginErrorReadLine ();
// ...while we read from the output stream synchronously:
string output=p.StandardOutput.ReadToEnd();
p.WaitForExit();
return (output, errors.ToString());
}
在.NET 5+(和.NET Core)中,UseShellExecute的默認值為false,而在.NET Framework中,為true。由于這是一項重大更改,因此在從 .NET Framework 移植代碼時,值得檢查對 Process.Start 的所有調(diào)用。
UseShellExecute 標志更改 CLR 啟動進程的方式。使用 UseShellExecute true,您可以執(zhí)行以下操作:
缺點是無法重定向輸入或輸出流。如果需要這樣做(在啟動文件或文檔時),解決方法是將 UseShellExecute 設(shè)置為 false 并使用“/c”開關(guān)調(diào)用命令行進程 (cmd.exe),就像我們之前調(diào)用 時所做的那樣。
在 Windows 下,UseShellExecute 指示 CLR 使用 Windows 函數(shù)而不是 函數(shù)。在 Linux 下,UseShellExecute 指示 CLR 調(diào)用 、 或 。
靜態(tài) System.AppContext 類公開了兩個有用的屬性:
此外,AppContext 類還管理布爾值的全局字符串鍵字典,旨在為庫編寫器提供一種標準機制,允許使用者打開或關(guān)閉新功能。這種非類型化方法對于您希望對大多數(shù)用戶保持未記錄的實驗性功能是有意義的。
庫的使用者請求啟用功能,如下所示:
AppContext.SetSwitch ("MyLibrary.SomeBreakingChange", true);
然后,該庫中的代碼可以檢查該開關(guān),如下所示:
bool isDefined, switchValue;
isDefined=AppContext.TryGetSwitch ("MyLibrary.SomeBreakingChange",
out switchValue);
如果開關(guān)未定義,則 TryGetSwitch 返回 false;這使您可以區(qū)分未定義的開關(guān)和值設(shè)置為 false 的開關(guān)(如有必要)。
具有諷刺意味的是,TryGetSwitch 的設(shè)計說明了如何不編寫 API。out 參數(shù)是不必要的,該方法應(yīng)返回一個可為空的布爾值,其值為 true、false 或 null 表示未定義。然后,這將啟用以下用途:
bool switchValue=AppContext.GetSwitch ("...") ?? false;
當前,網(wǎng)絡(luò)通訊問題已經(jīng)對企業(yè)業(yè)務(wù)的發(fā)展形成挑戰(zhàn),如何切實有效保障網(wǎng)絡(luò)通訊品質(zhì)?DVBCN&AsiaOTT在“2017亞太CDN峰會”期間就此問題采訪到了北京聯(lián)宇益通科技有限公司(NETPAS)VP劉杰。
劉杰先生于2011年加入NETPAS公司,負責公司整體ToB業(yè)務(wù)線,擁有超過十余年的互聯(lián)網(wǎng)通訊行業(yè)相關(guān)任職經(jīng)驗。
網(wǎng)絡(luò)通訊問題成挑戰(zhàn) “SD-WAN+”助力
網(wǎng)絡(luò)通訊問題主要包含以下幾個方面:公有云服務(wù)訪問速度得不到保障造成用戶流失;跨境數(shù)據(jù)中心與公有云之間同步質(zhì)量低,對于用戶的數(shù)據(jù)匯總、決策的實時要求產(chǎn)生阻礙;用戶自有數(shù)據(jù)中心與公有云 VPC 間通訊不穩(wěn)定;大量動態(tài)交互內(nèi)容由于網(wǎng)絡(luò)質(zhì)量不可控,造成實時性差、客戶滿意度下降;客戶端訪問跨境辦公/業(yè)務(wù)應(yīng)用響應(yīng)慢,辦公效率下降。
帶寬不足并不是所有問題所在,實際問題是帶寬使用不當。企業(yè)投入不菲自建或采用服務(wù)商的網(wǎng)絡(luò)加速服務(wù),用戶卻依舊會投訴卡頓。在廣域網(wǎng)加速技術(shù)中,除了常見的內(nèi)容分發(fā)網(wǎng)絡(luò)CDN、通過軟件實現(xiàn)更靈活的網(wǎng)絡(luò)配置和管理的SDN,SD-WAN作為一種新興技術(shù),也逐漸在網(wǎng)絡(luò)加速行業(yè)嶄露頭角。SD-WAN 與CDN不同,CDN是純靜態(tài)的東西,而SD-WAN 的交互性很強,聯(lián)宇益通(Netpas)與合作伙伴的關(guān)系是互為用戶、互為表里。
聯(lián)宇益通專注于提供高品質(zhì)互聯(lián)網(wǎng)通訊服務(wù),運營的 NETPAS 網(wǎng)絡(luò)覆蓋中國大陸地區(qū)的所有省份,互聯(lián)著全國 92 個不同的互聯(lián)網(wǎng)服務(wù)商的網(wǎng)絡(luò)。NETPAS 網(wǎng)絡(luò)還擁有歐美和東南亞主要國家以及港、臺地區(qū)的境外網(wǎng)絡(luò)節(jié)點和獨立的國際帶寬,是主要的提供有服務(wù)質(zhì)量保證網(wǎng)絡(luò)通信服務(wù)的通信服務(wù)商之一。
據(jù)了解,SD-WAN的基本原理可以理解為,在一個或多個的不同物理網(wǎng)絡(luò)或網(wǎng)絡(luò)服務(wù)之上建立一張“虛擬網(wǎng)絡(luò)”。SD-WAN是依賴于網(wǎng)絡(luò)結(jié)構(gòu)邊緣,管理如何連接用戶/站點,并將其映射到可用的物理連接上。SD-WAN不僅包括了流量與虛擬-物理網(wǎng)絡(luò)間的映射,也包括了網(wǎng)絡(luò)服務(wù)選項的管理,所以,虛擬網(wǎng)絡(luò)的用戶不需要介入到管道的管理工作中。Netpas 的“SD-WAN+”網(wǎng)絡(luò)平臺在全球部署了 400 多個節(jié)點,基于多運營商網(wǎng)絡(luò)和動態(tài)優(yōu)化的路由控制,Netpas通過對IP數(shù)據(jù)包的二次封裝,實現(xiàn)用戶的數(shù)據(jù)包可以繞開網(wǎng)絡(luò)中可能存在的擁堵點,在國內(nèi)、國際的各個自治域(AS)之間進行通訊。
“云+網(wǎng)”模式保障通訊服務(wù)品質(zhì)
近年來,隨著直播行業(yè)的火熱,競爭愈發(fā)激烈。直播這一行業(yè)不僅是傳統(tǒng)上傳-下載的網(wǎng)絡(luò)環(huán)境,因為實時互聯(lián)的需求,對上行網(wǎng)絡(luò)要求較高。然而,由于主播的廣泛分布性,上行數(shù)據(jù)流在到達視頻源站或經(jīng)過CDN服務(wù)器時,會出現(xiàn)丟包導致的延遲及卡頓,因此直播用戶的穩(wěn)定性無法被保證。
聯(lián)宇益通VP劉杰在2017亞太CDN峰會上演講
越來越多用戶選擇把自己核心服務(wù),更多服務(wù)部署到云端,聯(lián)宇益通提出“云+網(wǎng)”,相比于傳統(tǒng)的云廠商,“云+網(wǎng)”模式具備兩個明顯優(yōu)勢:其一是巧妙解決了跨運營商通訊所帶來的網(wǎng)絡(luò)質(zhì)量問題;另一方面該模式讓快速部署與低廉成本成為可能,進而大幅提升云平臺的競爭力。
IBM與Netpas與的合作就是采取的這種“云+網(wǎng)”模式,通過在IBM SoftLayer的自建數(shù)據(jù)中心、服務(wù)器群中部署Netpas路由器,對IP數(shù)據(jù)包進行二次封裝,讓用戶的通訊數(shù)據(jù)包通過Netpas平臺完成基于多運營商網(wǎng)絡(luò)、動態(tài)優(yōu)化的路由控制。據(jù)了解,Netpas已經(jīng)為云計算、視頻直播、跨境電商、游戲、通訊、醫(yī)療、在線教育等行業(yè)的企業(yè),以及運營商用戶提供企業(yè)實時通訊服務(wù)等業(yè)務(wù)品質(zhì)保障服務(wù)。
NETPAS 如何讓網(wǎng)絡(luò)通信更加靠譜?
網(wǎng)絡(luò)規(guī)模:擁有規(guī)模巨大的網(wǎng)絡(luò),具備全球覆蓋的能力,是網(wǎng)絡(luò)通信優(yōu)化運營的先決條件。
健壯的架構(gòu):貫穿網(wǎng)絡(luò)的設(shè)計、建設(shè)和運營全過程,健壯性和穩(wěn)定性始終是我們最優(yōu)先考慮的目標。
智能分析:根據(jù)實際業(yè)務(wù)特性,引入多元指標的品質(zhì)分析,更能滿足不同類型網(wǎng)絡(luò)應(yīng)用的保障需要。
IP 層技術(shù):核心優(yōu)化技術(shù)工作于 OSI 七層網(wǎng)絡(luò)模型的第三層即 IP 層,支持廣泛的網(wǎng)絡(luò)業(yè)務(wù)形態(tài)。
實時控制:技術(shù)支撐團隊 7x24 小時監(jiān)控網(wǎng)絡(luò)運行情況,豐富的運營管理經(jīng)驗為網(wǎng)絡(luò)通信保駕護航。
安全保障:采用符合業(yè)界標準的必要技術(shù)手段,遵循相關(guān)法律法規(guī),確保數(shù)據(jù)通信安全可靠。
Netpas在技術(shù)實踐中去做好每一次研發(fā),解決每一個BUG,將Netpas的每一個產(chǎn)品做到最好,這是Netpas所追求的創(chuàng)業(yè)的藍海。
CDN廠商來搭臺,專業(yè)公司來唱戲
劉杰表示,專業(yè)的事情交給專業(yè)的技術(shù)或者專業(yè)的公司做。
他認為,CDN在未來一定會長期存在,因為有用戶需要。CDN技術(shù)本身并不太適合做強交互的內(nèi)容,它可能更適合服務(wù)靜態(tài)內(nèi)容,或者可被緩存、可被抓取的內(nèi)容。但是直播、AR、VR、在線教育等等,企業(yè)會選擇CDN組網(wǎng),真正通訊品質(zhì)的事情還是需要專業(yè)的做動態(tài)通訊品質(zhì)保障公司來做,網(wǎng)絡(luò)通訊運營和CDN就是一種共存,共同為用戶服務(wù)。這幾年,越來越多的人和企業(yè)在關(guān)注通訊品質(zhì)保障的藍海市場,作為創(chuàng)業(yè)型技術(shù)公司的聯(lián)宇益通,希望能夠通過這么多年的技術(shù)積累、經(jīng)驗沉淀,幫助更多的企業(yè)真正解決問題,讓社會更多關(guān)注通訊品質(zhì)。