1、字符
在。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"
}
}
2、字符串
一个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对象的一个引用。若是不存在,就建立字符串的副本,将副本添加到内部哈希表中,并返回对这个副本的一个引用 www.yztrans.com
和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));// 'True'
}
在对ReferenceEquals方法的第一个调用中,s1s2中的"Hello"字符串对象的引用是不一样的,因此应该显示False.然而,若是在CLR的4.0版本上运行,会发现显示True.由于这个版本的CLR选择了忽略C#编译器中字符串不留用的设置。
5. 字符串池
编译源代码时,编译器必须处理每一个文本常量字符串,并在托管模块的元数据上嵌入字符串。若是同一个文本常量字符串在源代码中屡次出现,将全部这些字符串都嵌入元数据中,会陡然增大最终生成的文件。
为了解决这个问题,许多编译器(包括C#编译器)都只在模块的元数据中将文本常量字符串写入一次。引用该字符串的全部代码都会被修改,以引用元数据中的同一个字符串。编译器这种将单个字符串的多个实例合并为一个实例的作法,能够显着减小模块大小。
3、高效率构造字符串
因为String类型是一个不可变的字符串,因此FCL提供了另外一个名为System.Text.StringBuilder的类型,可利用它高效率得对字符串和字符进行动态代理,最后基于处理结果建立一个String.
从逻辑上说,StringBuilder对象包含一个字段,该字段引用了有Char结构构成的一个数组。可利用StringBuilder的各个成员来操做这个字符数组,高效率的缩短或更改字符串中的字符。若是字符串变大,超过已分配的字符数组的大小,StringBuilder会自动分配一个新的、更大的数组,复制字符,并开始使用新数组。前一个数组会被垃圾回收 www.jx-jf.com
4、获取对象的字符串表示:ToString
在。NET Framework中能够调用ToString方法来和获取任何对象的字符串表示。System.Objetc定义了一个public、virtual、无参的ToString方法,因此在任何类型的一个实例上都能调用该方法。在语义上,ToString返回表明对象当前值的一个字符串,并且这个字符串应该根据调用线程当前的语言文化进行格式化。
5、解析字符串来获取对象:Parse
能解析一个字符串的任何类型都提供了一个名为Parse的public static方法。该方法获取一个String对象,并返回类型的一个实例。从某种意义上说,Parse扮演了一个工厂的角色。在FCL中,全部数值类型,DateTime、TimeSpan以及一些其余类型均提供了Parse方法 www.yzyedu.com
git