在本章中,我会介绍使用C#时的一些重要而又不适合放到其余章节的主题,包括字符串操做、可空类型、Main方法、文档注释以及嵌套类型。编程
对于内部计算来讲0和1很适合,可是对于人类可读的输入和输出,咱们须要字符串。BCL提供了不少能让字符串操做变得更简单的类。
C#预约义的string类型表明了.NET的System.String类。对于字符串,最须要理解的概念以下。数组
string类型有不少有用的字符串操做成员,包括容许咱们进行诸如检测长度、改变大小写、链接字符串等一些有用的操做。下表列出了其中一些最有用的成员。
从上表中的大多数方法的名字来看,好像它们都会改变字符串对象。其实,它们不会改变字符串而是返回了新的副本。对于一个string,任何“改变”都会分配一个新的恒定字符串。
例如,下面的代码声明并初始化了一个叫作s的字符串。第一个WriteLine语句调用了s的ToUpper方法,它返回了字符串中全部字母为大写形式的副本。最后一行输出了s的值,能够看到,字符串并无改变。安全
string s = "Hi there."; Console.WriteLine("{0}",s.ToUpper()); //输出全部字母为大写的副本 Console.WriteLine("{0}", s); //字符串没有变
笔者本身编码时,发现上表中颇有用的一个方法是Split。它将一个字符串分隔为若干子字符串,并将它们以数组的形式返回。将一组按预约位置分隔字符串的分隔符传给Split方法,就能够指定如何处理输出数组中的空元素。固然,原始字符串依然不会改变。
下面的代码显示了一个使用Split方法的示例。在这个示例中,分隔符由空字符和4个标点符号组成。app
class Program { static void Main() { string s1="hi there!this,is:a string."; char[] delimiters={' ','!',',',':','.'}; string[] words=s1.Split(delimiters,StringSplitOption.RemoveEmptyEntries); Console.WriteLine("Word Count:{0}\n\rThe Words…",words.Length); foreach(string s in words) { Console.WriteLine(" {0}",s); } } }
StringBuilder类能够帮助你动态、有效地产生字符串,而且避免建立许多副本。框架
例如,下面的代码声明并初始化了一个StringBuilder类型的字符串,而后输出了它的值。第四行代码经过替换初始字符串的一部分改变了其实际对象。当输出它的值,隐式调用ToString时,咱们能够看到,和string类型的对象不一样,StringBuilder对象确实被修改了。ide
using System; using System.Text; class Program { static void Main() { StringBuilder sb = new StringBuilder( "Hi there."); Console.WriteLine( "{0}", sb.ToString()); sb.Replace( "Hi", "Hello"); Console.WriteLine( "{0}", sb.ToString()); } }
当依据给定的字符串建立了StringBuilder对象以后,类分配了一个比当前字符串长度更长的缓冲区。只要缓冲区能容纳对字符串的改变就不会分配新的内存。若是对字符串的改变须要的空间比缓冲区中的可用空间多,就会分配更大的缓冲区,并把字符串复制到其中。和原来的缓冲区同样,新的缓冲区也有额外的空间。
要获取StringBuilder对应的字符串内容,咱们只须要调用它的ToString方法便可。函数
字符串都是Unicode字符的数组。例如,字符串"25.873"是6个字符而不是一个数字。尽管它看上去像数字,可是咱们不能对它使用数学函数。把两个字符串进行“相加”只会串联它们。性能
如下语句给出了一个使用Parse方法语法的示例。注意,Parse是静态的,因此咱们须要经过目标类型名来调用它。ui
double dl = double.Parse("25.873"); ↑ ↑ 目标类型 要转换的字符串
如下代码给出了一个把两个字符串解析为double型值并把它们相加的示例:this
static void Main() { string s1 = "25.873"; string s2 = "36.240"; double dl = double.Parse(s1); double d2 = double.Parse(s2); double total = dl + d2; Console.WriteLine("Total: {0}", total); }
这段代码产生了以下输出:
关于Parse有一个常见的误解,因为它是在操做字符串,会被认为是string类的成员。其实不是,Parse根本不是一个方法,而是由目标类型实现的不少个方法。
Parse方法的缺点是若是不能把string成功转换为目标类型的话会抛出一个异常。异常是昂贵的操做,应该尽量在编程中避免异常。TryParse方法能够避免这个问题。有关TryParse须要知道的亟要事项以下。
以下代码演示了使用int.TryParse方法的例子:
class Program { static void Main() { string parseResultSummary; string stringFirst = "28"; int intFirst; 输入字符串 输出变置 ↓ ↓ bool success = int.TryParse( stringFirst, out intFirst ); parseResultSummary = success ? "was successfully parsed" :"was not successfully parsed"; Console.WriteLine( "String {0} {1}", stringFirst, parseResultSummary ); string stringSecond = "vt750"; int intSecond; 输入字符串 输出变最 ↓ ↓ success = int.TryParse( stringSecond, out intSecond ); parseResultSummary = success ? "was successfully parsed" :"was not successfully parsed"; Console.WriteLine( "String {0} {1}", stringSecond, parseResultSummary ); } }
在第3章中咱们已经介绍过了可空类型。你应该记得,可空类型容许咱们建立一个值类型变量而且能够标记为有效或无效,这样咱们就能够有效地把值类型设置为"null"。我原本想在第3章中介绍可空类型及其余内置类型,可是既然如今你对C#有了更深刻的了解,如今正是时候介绍其更复杂的方面。
复习一下,可空类型老是基于另一个叫作基础类型(underlying type)的已经被声明的类型。
要建立可空类型的变量,只须要在变量声明中的基础类型的名字后面加一个问号。
例如,如下代码声明了一个可空int类型的变量。注意,后缀附加到类型名--而不是变量名称。
后缀 ↓ int? myInt=28;
有了这样的声明语句,编译器就会产生可空类型并关联变量类型。可空类型的结构以下图所示。
使用可空类型基本与使用其余类型的变量同样。读取可空类型的变量返回其值。可是你必须确保变量不是null的,尝试读取一个null的变量会产生异常。
int? myInt1=15; if(myInt1!=null) { Console.WriteLine("{0}",myInt1); }
你能够像下面那样显式使用两个只读属性。读取可空类型的变量返回其值。可是你必须确保变量不是null的,尝试读取一个null的变量会产生异常。
可空类型和相应的非可空类型之间可轻松实现转换。有关可空类型转换的重要事项以下:
例如,下面的代码行显示了两个方向上的转换。第一行int类型的字面量隐式转换为int?类型的值,并用于初始化可空类型的变量。第二行,变量显式转换为它的非可空版本。
int? myInt1 = 15; // 将int隐式转换为 int? int regInt = (int) myInt1; // 将int?显式转换为int
能够将如下三种类型的值赋给可空类型的变量:
如下代码分別给出了三种类型赋值的示例:
int? myI1,myI2,myI3 myI1 = 28; //基础类型的值 myI2 = myI1; //可空类型的值 myI3 = null; //null Console.WriteLine("myI1: {0}, myI2: {1}", myI1, myI2);
标准算术运算符和比较运算符一样也能处理可空类型。还有一个特别的运算符叫作空接合运算符(null coalescing operator),它容许咱们在可空类型变量为null时返回一个值给表达式。
空接合运算符由两个连续的问号组成,它有两个操做数。
int? myI4 = null; 空接合运算符 ↓ Console.WriteLine("myI4: {0}", myI4 ?? -l); myI4 = 10; Console.WriteLine("myI4: {0}", myI4 ?? -1);
若是你比较两个相同可空类型的值,而且都设置为null,那么相等比较运算符会认为它们是相等的(==和!=)。
例如,在下面的代码中,两个可空的int被设置为null,相等比较运算符会声 明它们是相等的。
int? i1 = null,i2 = null; //都为空 if (i1 == i2) //返回true { Console.WriteLine("Equal"); }
至此,咱们已经看到了预约义的简单类型的可空形式。咱们还能够建立用户自定义值类型的可空形式。这就引出了在使用简单类型时没有遇到的其余问题。
主要问题是访问封装的基础类型的成员。一个可空类型不直接暴露基础类型的任何成员。例如,来看看下面的代码和下图中它的表示形式。代码声明了一个叫作MyStruct的结构(值类型),它有两个公共字段。
struct MyStruct { public int X; public int Y; public MyStruct(int xVal,int yVal) { X=xVal; Y=yVal; } } class Program { static void Main() { MyStruct? mSNull=new MyStruct(5,10); … } }
例如,如下代码使用以前声明的结构并建立告终构和它对应的可空类型的变量。在代码的第三行和第四行中,咱们直接读取结构变量的值。在第五行和第六行中,就必须从可空类型的Value属性返回的值中进行读取。
MyStruct mSStruct=new MyStruct(6,11); MyStruct? mSNull=new MyStruct(5,10); Console.WriteLine("mSStruct.X: {0}",mSStruct.X); Console.WriteLine("mSStruct.Y: {0}",mSStruct.Y); Console.WriteLine("mSNull.X: {0}",mSNull.Value.X); Console.WriteLine("mSNull.Y: {0}",mSNull.Value.Y);
Nullable<T>
可空类型经过一个叫作System.Nullable<T>
的.NET类型来实现,它使用了C#的泛型特性。C#可空类型的问号语法是建立Nullable<T>
类型变量的快捷语法,在这里T是基础类型。Nullable<T>
接受了基础类型并把它嵌入结构中,同时给结构提供可空类型的属性、方法和构造函数。
咱们可使用Nullable<T>
这种泛型语法,也可使用C#的快捷语法。快捷语法更容易书写和理解,而且也减小了出错的可能性。如下代码使用Nullable<T>
语法为以前示例中声明的 MyStruct 结构建立一个叫作mSNull的Nullable<MyStruct>
类型。
Nullable<MyStruct> mSNull = new Nullable<MyStruct>();
下面的代码使用了问号语法,彻底等同于Nullable<T>
语法:
MyStruc? mSNull=new MyStruct();
每个C#程序都必须有一个入口点--一个必须叫作Main的方法。
在贯穿本书的示例代码中,都使用了一个不接受参数而且也不返回值的Main方法。然而,一共有4种形式的Main能够做为程序的入口点。这些形式以下:
前面两种形式在程序终止后都不返回值给执行环境。后面两种形式则返回int值。若是使用返回值,一般用于报告程序的成功或失败,0一般用于表示成功。
第二种和第四种形式容许咱们在程序启动时从命令行向程序传入实参,也叫作参数。命令行参数的一些重要特性以下。
例如,下面叫作CommandLineArgs的程序接受了命令行参数并打印了每个提供的参数:
class Program { static void Main(string[] args) { foreach (string s in args) { Console.WriteLine(s); } } }
以下命令行使用5个参数执行CommandLineArgs程序。
CommandLineArgs Jon Peter Beth Julia Tammi
↑ ↑
可执行程序名 参数
前面的程序和命令行产生了以下的输出:
其余须要了解的有关Main的重要事项以下。
一个程序只能够包含Main的4种可用入口点形式中的一种声明。固然,若是你声明其余方法的名称为Main,只要它们不是4种入口点形式中的一种就是合法--可是,这样作是很是容易混淆的。
Main的可访问性
Main能够被声明为public或private。
然而,不管Main声明的访问级或所属类或结构的访问级別是什么,执行环境老是能访问Main。
默认状况下,当Visual Studio建立了一个项目时,它就建立了一个程序框,其中的Main是隐式private。若是须要,你随时能够添加public修饰符。
文档注释特性容许咱们以XML元素的形式在程序中包含文档(第19章介绍XML)。Visual Studio会帮助咱们插入元素,以及从源文件中读取它们并复制到独立的XML文件中。
下图给出了一个使用XML注释的概要。这包括以下步骤。
以前的Visual Studio版本包含了基本的文档编译器,可是它在Visual Studio 2005发布以前被删除了。微软公司正在开发一个叫作Sandcastle的新文档编译器,它已经被用来生成.NET框架的文档。从http://sandcastle.codeplex.com 可更详细地了解以及免费下载这个软件。
文档注释从3个连续的正斜杠开始。
例如,如下代码中前4行就是有关类定义的文档注释。这里使用<summary>
XML标签。在字段声明之上有3行来讲明这个字段--仍是使用<summary>
标签。
///<summary> ← 类的开始XML标签 /// This is class MyClass, which does the following wonderful things, using /// the following algorithm. …Besides those, it does these additional /// amazing things. ///</summary> ← 关闭 XML 标签 class MyClass { ///<summary> /// Field1 is used to hold the value of … ///</summary> public int Field1 = 10; … }
每个XML元素都是当咱们在语言特性(好比类或类成员)的声明上输入3条斜杠时,ViSual Studio 自动增长的。
例如,从下面的代码能够看到,在MyClass类声明之上的2条斜杠:
// class MyClass {…}
只要咱们增长了第三条斜杠,Visual Studio会当即扩展注释为下面的代码,而咱们无须作任何事情。而后咱们就能够在标签之间输入任何但愿注释的行了。
/// <summary> 自动插入 /// 自动插入 /// </summary> 自动插入 class MyClass {…}
在以前的示例中,咱们看到了summay XML标签的使用。C#可识别的标签还有不少。下表列出了最重要的一些。
咱们一般直接在命名空间中声明类型。然而,咱们还能够在类或结构中声明类型。
例如,如下代码显示了MyClass类,其中有一个叫作MyCounter的嵌套类。
class MyClass //封闭类 { class MyCounter//嵌套类 {…} … }
若是一个类型只是做为帮助方法而且只对封闭类型有意义,可能就须要声明为嵌套类型了。不要跟嵌套这个术语混淆。嵌套指声明的位置--而不是任何实例的位置。尽管嵌套类型的声明在封闭类型的声明以内,但嵌套类型的对象并不必定封闭在封闭类型的对象以内。嵌套类型的对象(若是建立了的话)和它没有在另外一个类型中声明时所在的位置同样。
例如,下图显示了前面代码框架中的MyClass对象和MyCounter对象。另外还显式了MyClass类中的一个叫作Counter的字段,这就是指向嵌套类型对象的引用,它在堆的另外一处。
如下代码把MyClass和MyCounter完善成了完整的程序。MyCounter实现了一个整数计数器,从0开始而且使用++运算符来递增。当MyClass的构造函数被调用时,它建立嵌套类的实例而且为字段分配引用,下图演示了代码中对象的结构。
class MyClass { class MyCounter { public int Count{get;private set;} public static MyCounter operator ++(MyCounter current) { current.Count++; return current; } } private MyCounter counter; public MyClass(){counter=new MyCounter();} public int Incr(){return (counter++).Count;} public int GetValue(){return counter.Count;} } class Program { static void Main() { var mc=new MyClass(); mc.Incr();mc.Incr();mc.Incr(); mc.Incr();mc.Incr();mc.Incr(); Console.WriteLine("Total: {0}",mc.GetValue()); } }
在第7章中,咱们已经了解到类和类型一般有public或internal的访问级别。然而,嵌套类型的不一样之处在于,它们有成员访问级别而不是类型访问级别。所以,下面的命题是成立的。
在这两种状况下,嵌套类型的默认访问级别都是private,也就是说不能被封闭类型之外的对象所见。
封闭类和嵌套类的成员之间的关系是很容易理解的,以下图所示。无论封闭类型的成员声明了怎样的访问级別,包括private和protected,嵌套类型都能访问这些成员。
然而,它们之间的关系不是对称的。尽管封闭类型的成员老是可见嵌套类型的声明而且能建立它的变量及实例,可是它们不能彻底访问嵌套类型的成员。相反,这种访问权限受限于嵌套类成员声明的访问级别--就好像嵌套类型是一个独立的类型同样。也就是说,它们能够访问public或internal的成员,可是不能访问嵌套类型的private或protected成员。
咱们能够把这种关系总结以下。
嵌套类型的可见性还会影响基类成员的继承。若是封闭类型是一个派生类,嵌套类型就能够经过使用相同的名字来隐藏基类成员。能够在嵌套类型的声明上使用new修饰符来显式隐藏。
嵌套类型中的this引用指的是嵌套类型的对象--不是封闭类型的对象。若是嵌套类型的对象须要访问封闭类型,它必须持有封闭类型的引用。如如下代码所示,咱们能够把封闭对象提供的this引用做为参数传给嵌套类型的构造函数:
class SomeClass //封闭类 { int Field1=15,Field2=20; //封闭类的字段 MyNested mn=null; //嵌套类的引用 public void PrintMyMembers() { mn.PrintOuterMembers(); //调用嵌套类中的方法 } public SomeClass() //构造函数 { mn=new MyNested(this); //建立嵌套类的实例 } class MyNested //嵌套类声明 { SomeClass sc=null; //封闭类的引用 public MyNested(SomeClass SC)//嵌套类构造函数 { sc=SC; //存储嵌套类的引用 } public void PrintOuterMembers() { Console.WriteLine("Field1: {0}",sc.Field1);//封闭字段 Console.WriteLine("Field2: {0}",sc.Field2);//封闭字段 } } //嵌套类结束 } class Program { static void Main() { var MySC=new SomeClass(); MySC.PrintOuterMembers(); } }
第6章介绍了建立类对象的构造函数。类还能够拥有析构函数(destructor),它能够在一个类的实例再也不被引用的时候执行一些操做,以清除或释放非托管资源。非托管资源是指相似用Win32 API或非托管内存块获取的文件句柄这样的资源。使用.NET资源是没法获取它们的,所以若是咱们只用.NET类,是不须要编写太多析构函数的。
关于析构函数要注意如下几点。
例如,下面的代码经过类Class1演示了析构函数的语法:
Class1 { ~Class1() { CleanupCode } … }
使用析构函数时一些重要的原则以下:
在C#3.0发布以前,析构函数有时也叫终结器(finalizer)。你可能会常常在文本或.NET API方法名中遇到这个术语。
与C++析构函数不一样,C#析构函数不会在实例超出做用域时当即调用。事实上,你没法知道什么时候会调用析构函数。此外,如前所述,你也不能显式调用析构函数。你所能知道的只是,系统会在对象从托管堆上移除以前的某个时刻调用析构函数。
若是你的代码中包含的非托管资源越快释放越好,就不能将这个任务留给析构函数,由于没法保证它会什么时候执行。相反,你应该采用标准dispose模式。
标准dispose模式包含如下特色。
可能会有点混乱,因此咱们再总结一下。你想将全部清除代码放到Dispose方法中,并在使用完资源时调用。以防万一Dispose没有调用,类的析构函数也应该调用Dispose。而另外一方面若是调用了Dispose,你就但愿通知垃圾回收器不要再调用析构函数,由于已经由Dispose执行了清除操做。析构函数和Dispose代码应该遵循如下原则。
下面的代码展现了标准的dispose模式,下图对其进行了阐释。这段代码的要点以下:
下表对什么时候调用构造函数和析构函数进行了总结和比较。
尽管本书不介绍COM编程,可是C#4.0专门增长了几个语法改变,使得COM编程更容易。其中的一个改变叫作“省略ref”特性,容许不须要使用方法返回值的状况下,无需ref关键字便可调用COM方法。
例如,若是程序所在的机器上安装了微软Word,你就能够在本身的程序中使用Word的拼写检査功能。这个方法是 Microsoft.Office.Tools.Word 命名空间的Document类中的CheckSpelling方法。这个方法有12个参数,且都是ref参数。也就是说,以前即便你不须要为方法传入数据或是从方法取回数据,也只能为每个参数提供一个引用变量。省略ref关键字只能用于COM方法, 不然就仍然会收到编译错误。
代码差很少应该以下,对于这段代码注意几点。
object ignoreCase=true; object alwaysSuggest=false; object optional=Missing.Value; tempDoc.CheckSpelling(ref optional,ref ignoreCase,ref alwaysSuggest, ref optional,ref optional,ref optional,ref optional,ref optional, ref optional,ref optional,ref optional,ref optional);
有了“省略ref”特性,咱们的代码就干净多了,由于对于不须要输出的参数,咱们再也不须要使用ref关键字,只须要为咱们关心的两个参数使用内联的bool。简化后的代码以下:
object optional=Missing.Value; tempDoc.CheckSpelling(optional,true,false, optional,optional,optional,optional,optional, optional,optional,optional,optional);
除了“省略ref”特性,对于可选的参数咱们可使用C#4.0的可选参数特性,比以前的又简单不少,以下所示:
tempDoc.CheckSpelling( Missing.Value, true, false );
以下代码是一个包含这个方法的完整程序。要编译这段代码,你须要在本机上安装 Visual Studio Tools for Office(VSTO)而且必须为项目添加 Microsoft.Office.Interop.Word 程序集的引用。要运行这段编译的代码,必须在本机上安装 Microsoft Word。
using System; using System.Reflection; using Microsoft.Office.Interop.Word; class Program { static void Main() { Console.WriteLine("Enter a string to spell-check"); string stringToSpellCheck=Console.ReadLine(); string spellingResults; int errors=0; if(stringToSpellCheck.Length==0) { spellingResults="No string to check"; } else { Microsoft.Office.Interop.Word.Application app= new Microsoft.Office.Interop.Word.Application(); Console.WriteLine("\nChecking the string for misspellings …"); app.Visible=false; Microsoft.Office.Interop.Word._Document tempDoc=app.Document.Add(); tempDoc.Words.First.InsertBefore(stringToSpellCheck); Microsoft.Office.Interop.Word.ProofreadingErrors spellErrorsColl= tempDoc.SpellingErrors; errors=spellErrorsColl.Count; // 1.不是用可选参数 // object ignoreCase=true; // object alwaysSuggest=false; // object optional=Missing.Value; // tempDoc.CheckSpelling(ref optional,ref ignoreCase,ref alwaysSuggest, // ref optional,ref optional,ref optional,ref optional,ref optional, // ref optional,ref optional,ref optional,ref optional); // 2.使用C#4.0的“省略ref”特性 object optional=Missing.Value; tempDoc.CheckSpelling(optional,true,false, optional,optional,optional,optional,optional, optional,optional,optional,optional); //3.使用“省略ref”和可选参数特性 app.Quit(false); spellingResults=errors+" errors found"; } Console.WriteLine(spellingResults); Console.WriteLine("\nPress <Enter> to exit program"); Console.WriteLine(); } }
若是你运行这段代码,会获得如图25-8所示的一个控制台窗口,它会要求你输入但愿进行拼写检査的字符串。在收到宇符串以后它会打开Word而后运行拼写检査。此时,你会看到出现了一个Word的拼写检査窗口,如图25-9所示。
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">