一、字符
在.NET Framewole中,字符總是表示成16位Unicode代碼值,這簡化了國際化應用程序的開發。
每個字符都表示成System.Char結構(一個值類型) 的一個實例。System.Char類型提供了兩個公共只讀常量字段:MinValue(定義成"/0")和MaxValue(定義成'/uffff')。 針對Char的一個實例,可以調用GetUnicodeCategory方法,這個方法返回的是System.Globalization.UnicodeCategory枚舉類型的一個值。這個值支出該字符是控制字符、貨幣符號、小寫符號、大寫符號、標點符號、數學符號,還是其他字符(Unicode定義的字符)。 為了簡化開發,Char類型還提供了幾個靜態方法,比如IsDigit,IsLotter,IsUpper,IsLower,isNumber等。其中大多數方法都在內部調用了GetUnicodeCategory,并簡單的返回true和false。另外可調用ToLowerInvariant和ToUpperInvariant,以忽略語言文化(culture)的方式,將一個字符轉換成小寫和大寫形式。作為另一種替代方案,ToLower和ToUpper方法將字符轉換成小寫和大寫形式,但是轉換時要使用與調用線程關聯的靜態CurrenCulture屬性來獲得。ToLower和ToUpper之所以需要語言文化信息,是因為字母大小寫的轉換是依賴于語言文化的。不同的語言文化,大小寫的形式也不盡相同。 可以使用三種技術實現各種數值類型與Char實例的相互轉換: 1) 轉型(強制類型轉換) 要將一個Char轉換成為一個數值(比如Int32),最簡單的方法就是轉型。在三種技術中,效率也是最高的,因為編譯器會生成中間語言(IL)指令來執行轉型,不必調用任何方法。 2) 使用Convert類型 System.Convert類型提供了幾個靜態方法來實現Char和數值類型的相互轉型。所以這些方法都是以checked方式進行轉換,所以一旦發現轉型會造成數據丟失,就會拋出OverflowException異常。 3) 使用IConvertible接口 Char類型和FCL的所有數值類型都實現的ICOnvertible接口。該接口定義了像ToUInt16和ToChar這些的方法。這種技術效率最差,因為在值類型上調用一個接口方法,要求對實例進行裝箱——Char和所有數值類型都是值類型。如果某個類型不能轉換,或者轉換造成數據的丟失,IConvertible的方法會拋出System.InvalidCastException異常。 下面演示這三種方法的調用:internal static class CharConvert { public static void Go() { Char c; Int32 n; // 使用C#轉型技術實現,強制類型轉換 c = (Char)65; Console.WriteLine(c); // 顯示 "A" n = (Int32)c; Console.WriteLine(n); // 顯示 "65" c = unchecked((Char)(65536 + 65)); Console.WriteLine(c); // 顯示 "A" // 使用Convert進行轉換 c = Convert.ToChar(65); Console.WriteLine(c); // 顯示 "A" n = Convert.ToInt32(c); Console.WriteLine(n); // Displays "65" // 顯示Convert的范圍檢查 try { c = Convert.ToChar(70000); // 對 16-bits 來說過大 Console.WriteLine(c); // 不知心 } catch (OverflowException) { Console.WriteLine("Can't convert 70000 to a Char."); } // 使用IConvertible進行轉換 c = ((IConvertible)65).ToChar(null); Console.WriteLine(c); // 顯示 "A" n = ((IConvertible)c).ToInt32(null); Console.WriteLine(n); // 顯示 "65" }}二、字符串
一個String代表一個不可變(immutable)的順序字符集。String直接派生自Object,所以它是一個引用類型。因此,String(字符串數組)總是存在于堆上,不會跑到棧上去。 String類型還實現了幾個接口,IComparable、ICloneable等。 1.構造字符串 許多編程語言(包括C#)都將String視為一個基元類型——也就是說,編譯器允許在源代碼中直接表示文本常量字符型。編譯器將這些文本常量字符串放到模塊的元數據中,并在運行時加載和引用它們。 在C#中,不能使用new操作符從一個文本常量字符串構造一個String對象,相反,必須使用簡化的語法表示:class PRogram { private static void Main(string[] args) { String s = "Hi"; Console.WriteLine(s); } }
編譯上述代碼,并檢查它的IL,會看到一下內容:
.method private hidebysig static void Main(string[] args) cil managed{ .entrypoint .maxstack 1 .locals init ( [0] string str) L_0000: nop L_0001: ldstr "Hi" L_0006: stloc.0 L_0007: ldloc.0 L_0008: call void [mscorlib]System.Console::WriteLine(string) L_000d: nop L_000e: ret } IL指令的newobj用于構造一個對象的新實例。然而,上述IL代碼并沒有出現newobj之類,只有一個特殊的ldstr(即load string)指令,它用從元數據獲得的一個文本常量字符串構造一個String對象。這證明CLR事實是用一種特殊的方式構造文本常量String對象。 C#提供了一些特殊的語法來幫助開發人員在源代碼中輸入文本常量字符串。對于換行符、回車符和退格這樣的特殊字符,C#采用了C/C++的轉義機制://包含回車符和換行符的字符串String s ="Hi/r/nthere"
但是,一般不建議這么做。因為在不同的平臺解釋是不同的,推薦使用System.Environment中定義的NewLine屬性。NewLine屬性是依賴于平臺的,他會一句底層平臺返回恰當的字符串。
若要在運行時將幾個字符串連接到一起,請避免使用+操作符,因為它會在堆上創建多個字符串對象,而對象是需要垃圾回收的,從而影響性能。相反,應盡量使用System.Text.StringBuilder類型。 最后,C#還提供了一種特殊的字符串聲明方式(@"xxx")。采用這種方式,引號之間的所有字符都會被視為字符串的一部分。這種特殊聲明稱為"逐字字符串",通常用于指定文件或目錄的路徑,或者配合正則表達式使用。 2.字符串是不可變的 String對象最重要的一個事實就是,它是不可變的(immytable)。也就是說,字符串一經創建就不能更改,不能變長、變短或修改其中任何字符。字符串不可變也下面幾點好處: 1.它允許在字符串上執行任何操作,而不實際的更改字符串。 2.在操作或訪問字符串時不會發生線程同步問題。 3.CLR可通過一個String對象共享多個完全一致的String內容。這樣能減少系統中的字符串屬性,從而節省內存,這就是"字符串留用"技術的目的。 考慮到性能方面的原因,String類型和CLR是緊密集成的。具體的說,CLR知道String類型中定義的字段是如何布局的,而且CLR會直接訪問這些字段。但是,為了獲得這種性能和直接訪問的好處,開發時只好將String定義為密封類。 3.比較字符串 判斷字符串相等性或對字符串進行排序時,強烈建議調用下面列出的方法之一:bool Equals (string value, StringComparison comparisonType)static bool Equals (string a, string b, StringComparison comparisonType) static int Compare (string strA, string strB, StringComparison comparisonType)static int Compare (String strA, String strB, bool ignoreCase, CultureInfo culture)static int Compare (string strA, string strB, CultureInfo culture, CompareOptions options)static int Compare (string strA, int indexA, string strB, int indexB, int length, StringComparison comparisonType)static int Compare (string strA, int indexA, string strB, int indexB, int length, CultureInfo culture, CompareOptions options)static int Compare (String strA, int indexA, String strB, int indexB, int length, bool ignoreCase, CultureInfo culture)
進行排序時應該總是執行區分大小寫的比較。原因是假如只是大小寫不同的兩個字符串被視為相等,那么每次對它們進行派時許,它們都可能按照不同的順序排列,從而造成用戶的迷惑。
上述代碼中的comparisonType參數要求獲取由System.StringComparison枚舉類型定義的某個值。這個枚舉類型是這樣定義的:public enum StringComparison { //使用區域敏感排序規則和當前區域比較字符串。CurrentCulture, //使用區域敏感排序規則、當前區域來比較字符串,同時忽略被比較字符串的大小寫。CurrentCultureIgnoreCase, //使用區域敏感排序規則和固定區域比較字符串。InvariantCulture, //使用區域敏感排序規則、固定區域來比較字符串,同時忽略被比較字符串的大小寫。InvariantCultureIgnoreCase, //使用序號排序規則比較字符串。Ordinal, //使用序號排序規則并忽略被比較字符串的大小寫,對字符串進行比較。OrdinalIgnoreCase}另外,前面有兩個方法要求傳遞一個CompareOptions參數。這個參數要獲取有CompareOptions枚舉類型定義的一個值:
public enum CompareOptions { None = 0, //指示字符串比較必須忽略大小寫。IgnoreCase = 1, //指示字符串比較必須忽略不占空間的組合字符,比如音調符號。IgnoreNonSpace = 2, //指示字符串比較必須忽略符號,如空白字符、標點符號、貨幣符號、百分號、數學符號、“&”符等等IgnoreSymbols = 4, //指示字符串比較必須忽略 Kana 類型IgnoreKanaType = 8, //指示字符串比較必須忽略字符寬度IgnoreWidth = 16, //指示字符串比較必須使用字符串排序算法。StringSort = 0x20000000, //指示必須使用字符串的連續 Unicode UTF-16 編碼值進行字符串比較(使用代碼單元進行代碼單元比較),這樣可以提高比較速度,但不能區分區域性Ordinal = 0x40000000, //字符串比較必須忽略大小寫,然后執行序號比較。OrdinalIgnoreCase = 0x10000000} 接受一個CompareOptions實參的方法要求你必須顯式傳遞一個語言文化。如果傳遞了Ordinal或OrdinalIgnoreCase 標志,這些Comoare方法會忽略指定的語言文化。
許多程序都將字符串用于內部編程目的,比如路徑名、文件名、URL、注冊表項/值等等。這些字符串通常只在程序內部使用,不會向用戶顯示。出于編程目的而比較字符串時,應該總是使用StringComparison.Ordinal或者CompareOptions.OrdinalIgnoreCase。這是字符串比較時最快的一種方式,因為在執行比較時,不需要考慮語言文化信息。 另一方面,如果想以一種語言文化正確的方式來比較字符串(通常顯示給用戶),應該使用StringComparison.CurrentCulture或者StringComparison.CurrentCultureIgnoreCase。 提示:StringComparison.InvariantCulture和StringComparison.InvariantCultureIgnoreCase平時最好不要用。雖然這兩個值能保證比較是語言文化的正確性,但用它們比較用于內部編程目的的字符串,花費的事件要比執行一次序號比較長的多。 提示:執行序號比較之前,如果(想更改字符串中的字符的大小寫,應該使用String的ToUpperInvariant和ToLowerInvariant方法。對字符串進行正規化時,強烈建議使用ToUpperInvariant方法,而不要使用ToLowerInvariant方法,應為Microsoft對執行大寫比較的代碼進行了優化。事實上,執行不需要區分大小寫的比較之前,FCL會自動將字符串正規化為大寫形式。之所以不用ToUpper和ToLower方法,是因為它們對語言文化敏感。4.字符串留用 檢查字符串的相等性是許多應用程序的常見操作——這個任務可能驗證損害性能。執行序號(ordinal)相等性檢查時,CLR快速測試兩個字符串是否包含相同數量的字符。如果是否定,字符串肯定不相等;如果肯定,字符串可能相等。然后,CLR必須比較每個單獨的字符才能確定。值得注意的是,在執行需要注意語言文化的比較是,CLR始終都要比較所有單獨的字符,因為兩個字符串即使長度不同,也可能是相等的。 除此之外,如果在內存中復制同一個字符串的多個實例,會造成內存的浪費,因為字符串是"不可變"的。如果只在內存中保留字符串的一個實例,那么將顯著提高內存的利用率。需要引用字符串的所有變量只需指向單獨一個字符串對象。 如果引用程序經常對字符串進行區分大小寫、序號式比較,或者事先知道許多字符串對象都有相同的值,就可以利用CLR的"字符串留用"機制來顯著提高性能。CLR初始化時會創建一個內部哈希表。在i這個表中,鍵(key)是字符串,而值(value)是對托管堆中String對象的引用。哈希表最開始是空的,String類提供了兩個方法,便于你訪問這個內部哈希表://檢索系統對指定 System.String 的引用public static string Intern(string str)//檢索對指定 System.String 的引用public static string IsInterned(string str)
第一個方法Intern獲取一個String,獲得它的哈希碼,并在內部哈希表中檢查是否有匹配的。如果存在一個完全相同的字符串,就返回對這個字符串已經存在的String對象的一個引用。如果不存在,就創建字符串的副本,將副本添加到內部哈希表中,并返回對這個副本的一個引用。
和Intern方法一樣,IsInterned方法也獲取一個String,并在內部哈希表中查找它。如果哈希表中有一個匹配的字符串,IIsInterned就返回對這個留用的字符串對象的一個引用。然而,如果哈希表中沒有一個相匹配的字符串,IsInterned會返回null;它不會將字符串添加到哈希表中。 一個程序集加載時,CLR默認會留用程序集的元數據中描述的所有文本常量(literal)字符串。Microsoft知道可能因為額外的哈希表查找會造成性能顯著下降,所以現在是可以禁用這個"特性"的。 根據ECMA規范,CLR可能選擇不留用那個程序集的元數據中定義的所有字符串。即使指定了CLR不留用那個程序集中的字符串,但是CLR也可能選擇對字符串進行留用,但不應該依賴于CLR的這種"自主"行為。事實上,除非自己顯式調用String的Intern方法,否則永遠都不要以"字符串已留用"為前提來寫自己的代碼。以下代碼演示了字符串留用: public static void Go() { String s1 = "Hello"; String s2 = "Hello"; Console.WriteLine(Object.ReferenceEquals(s1, s2));// 'False' s1 = String.Intern(s1); s2 = String.Intern(s2); Console.WriteLine(Object.ReferenceEquals(s1, s2));// 'T
新聞熱點
疑難解答