?
第一章 FRAMEWORK 基本原理 第二课:公共引用类型的使用:
在.NET Framework中大部分的类型是引用类型。引用类型提供了很大的灵活性,并且它们提供了完美的性能去把它们传到方法中。以下部分通过讨论公共内建的类介绍了引用类型。在第四课“类型的转化”中将会包含创建class,interface,和delegate.
?
学完这课后,你将能够:
?
■?解释值类型与引用类型的不同。
■描述当赋值时,值类型与引用类型有什么不同。
■列出内建的引用类型。
■描述你何时使用StringBuilder类型。
■创建数组和数组内的排序。
■打开,读,写,和关闭文件。
■监测什么时候异常会发生并且对异常做如何反映。
?
什么是引用类型?
?
引用类型存储着引用类型的数据的地址,也被称为指针,并且引用类型在stack中。这个地址所指向的?实际数据被存储在一个被称为heap的内存区域中。运行时通过一个叫做garbage collection去管理被heap使用的内存。Garbage collection阶段性通过抛弃不再被引用的项去回收内存为以后使用。
Garbage collection
Garbage collection?仅仅在需要的时候,和触发了GC.Collect?的调用时发生。自动垃圾回收机制使应用中的大部分实例是短命的,除非它们是应用已开始被分配的。这样的设计模式导致了最好的性能。
?
引用和值类型的行为比较:
因为引用代表的是数据的地址而不是数据本身,把一个引用类型赋给另一个引用类型并不是拷贝数据,而仅仅是创建引用的一个副本,副本和初始变量引用堆中的相同的内存地址。
?
考虑以下简单的struct声明:
// C#
struct Numbers
{
public int val;
public Numbers(int _val)
{ val = _val; }
public override string ToString()
{ return val.ToString(); }
}
现在考虑以下代码,创建了一个Numbers的结构,并把一个副本拷给了第二个实例,修改两个值。
// C#
Numbers n1 = new Numbers(0);
Numbers n2 = n1;
n1.val += 1;
n2.val += 2;
Console.WriteLine("n1 = {0}, n2 = {1}", n1, n2);
?
代码将显示“n1=1,n2=2”因为结构是一个值类型,并且拷贝一个值类型导致两个不同的值。待如果把Numbers变为class,这个代码结果就为“n1=3,n2=3”。把Numbers从结构转变为类使它成为一个引用类型。当你修改一个引用类型时,你修改的是引用类型的所以副本。
?
?
?
?
内建引用类型
在.NET Framework中有大约2500个内建引用类型。凡是不继承System.ValueType的都是引用类型。表1-3中列出大部分的公共用途的类型,被许多其它的引用类型所继承。
Table 1-3?公共引用类型
Type ?????????????????????????????????????????????????????????????? Use for
System.Object ??????????????Object?类型是Framework中最普遍的类型,你能把任何类型转化为??? S????????????????????????????? System.Object.并且你能使用任何类型的继承Object类的??????
??????? ToString, GetType, and Equals?成员。
?
System.String ???????????????文本数据。
?
System.Text.StringBuilder ?动态文本数据。
?
System.Array ?????????????????数据数组.?这是所有数组的基类。
?
System.IO.Stream ????????????文件设备和I/O网络的缓冲。这是一个抽象类;特定的类
都继承Stream
?
System.Exception ????????????处理系统和应用定义的异常。特定的异常继承此类型。
?
?
?
Strings and String Builders
这两个类型不仅仅是数据的容器,它们也通过它们的成员去操作数据。
System.String?提供一系列成员去操作文本。例如以下代码快速的搜索和替代。
// C#
string s = "this is some text to search";
s = s.Replace("search", "replace");
Console.WriteLine(s);
System.String类型中的字符串在.NET中是具有恒定性的。这意味着对字符串的任何改变都会引起运行时去创建一个新的字符串并放弃旧的字符串。许多程序员很奇怪的发现如下代码在内存中分配了四个新的字符串:
string s;
s = "wombat"; // "wombat"
s += " kangaroo"; // "wombat kangaroo"
s += " wallaby"; // "wombat kangaroo wallaby"
s += " koala"; // "wombat kangaroo wallaby koala"
Console.WriteLine(s);
仅仅最后的字符串有一个引用;其它三个将被垃圾回收销毁。避免这些临时字符串能够避免不必的垃圾回收,这样可以提升性能。有?三种方法避免临时字符串:
■使用String类中的Concat,Join, or Format?方法在一个单独的语句去连接多个项。
■?使用StringBuilder class?去动态地创造易变的字符串.
?
默认的构造器创建一个16 bytes?长的缓冲,并随着需求而增长。
你能定义一个初始大小和最大值。如下所示:
System.Text.StringBuilder sb = new System.Text.StringBuilder(30);
sb.Append("wombat"); // Build string.
sb.Append(" kangaroo");
sb.Append(" wallaby");
sb.Append(" koala");
string s = sb.ToString(); // Copy result to string.
Console.WriteLine(s);
?
String class另一个精巧且重要的是它重载了System.Object.的操作符。
表1-4列出了String class重载的操作符:
?
?
Table 1-4 String?操作符
Operator ??????????C# ?????????????Action on System.String
?
Addition ??????????+ ??????????????连接两个字符串创建一个新的字符串。
?
?
Equality ??????????== ????????????如果两个字符串有相同的内容返回True
如果不同返回False
??????????????????????????????????? ?
Inequality ???????!= ?????????????相等操作符的相反操作.
?
Assignment ???????= ??????????????把一个字符串拷贝到另一个。它引发字符串的行为像值类型一样。?????????????????? ?????????????????????????? ???即使它们是作为引用类型来实现的。?????????????????????????????????????
??????????????????????????????????
?
如何创建数组以及数组的排序
使用方括号来声明数组。对于String类型,System.Array提供了方法对它包含的数据进行操作。以下的代码用一些初始数据声明了一个数组,然后对这个数组排序。
// C#
// Declare and initialize an array.
int[] ar = { 3, 1, 2 };
// Call a shared/static array method.
Array.Sort(ar);
// Display the result.
Console.WriteLine("{0}, {1}, {2}", ar[0], ar[1], ar[2]);
?
?
如何使用流
流是另一个非常重要的通用类型,因为流是为了从硬盘中读和写和跨网络通信。System.IO.Stream类型是所有特定的流类型的基类。表?1-5显示了一些最普遍使用的流类型。同时,网络流System.Network.Sockets命名空间中,加密流在System.Security.Cryptography空间里。
最简单的流类是StreamReader?和?StreamWriter,它们能使你对文本文件读和写。你能传递一个文件名作为构造器的一部分,用一行代码就能打开一个文件。在你处理一个文件后,调用Close方法以至于文件不会被锁。以下的?代码通过System.IO namespace,显示如何从一个文本文件中读和写:
// Create and write to a text file
StreamWriter sw = new StreamWriter("text.txt");
sw.WriteLine("Hello, World!");
sw.Close();
// Read and display a text file
StreamReader sr = new StreamReader("text.txt");
Console.WriteLine(sr.ReadToEnd());
sr.Close();
Table 1-5?通用的流类型:
System.IO?类型???????????????? ????????用于
FileStream ???????????????创建一个基本流用于去写文件和读文件
?
MemoryStream ?????????????创建一个基本流在内存中读和写
?
StreamReader ?????????????从流中读数据
?
StreamWriter ????????????向流中写数据
?
?
如何抛出和捕捉异常:
异常是不期望的事件,它们干扰了一个assembly?的正常执行。比如,如果你的assembly?正在从一个不存在的硬盘中读大量的文本,runtime?将会抛出一个异常。异常的发生是明智的,因为这样你的assembly无法进行运行。在以前的例子中,你能通知使用者文件不能用,然后等待着来自使用者的命令。简单的例子如下:
try
{
StreamReader sr = new StreamReader(@"C:"boot.ini");
Console.WriteLine(sr.ReadToEnd());
}
catch (Exception ex)
{
// If there are any problems reading the file, display an error message
Console.WriteLine("Error reading file: " + ex.Message);
}
在上面的例子中,如果有任何错误发生—包括一个文件找不到了,没有充足的权限,或在读文件时有错误—这时执行会在Catch块中继续执行下去。
如果没有问题发生,运行时将会忽略Catch块。基本的Exception类非常有用并且包含了一个错误信息和其它的应用数据。除了基本的Exception类,Framework还定义了几百个Exception类去描述不同的事件,所有的异常类都继承System.SystemException。另外,当你需要比标准异常更多的细节去描述一个事件定义你的异常时,你可以通过继承System.ApplicationException或System. Exception去定义自己的异常。
定义多个异常类许可你对不同的错误作出不同的反应。如果第一个Catch?块符合异常类型,runtime只会运行第一个Catch?块,所以Catch应该按从“最特殊到最不特殊(most-specific to least-specific,从特殊到一般)”的次序排列下去。以下的代码为了一个找不到文件的错误显示不同的错误信息,一个权限不够的错误,和其它任何可能发生的错误:
' VB
Try
Dim sr As StreamReader = New StreamReader("text.txt")
Console.WriteLine(sr.ReadToEnd)
Catch ex As System.IO.FileNotFoundException
Console.WriteLine("The file could not be found.")
Catch ex As System.UnauthorizedAccessException
Console.WriteLine("You do not have sufficient permissions.")
Catch ex As Exception
Console.WriteLine("Error reading file: " + ex.Message)
End Try
这个过程有时被称为异常过滤。捕捉异常也支持Finally块。Finally块运行在Try块之后并且任何Catch块要进行这个结束执行,无论一个异常是否抛出。因此你应该使用一个Finally块去关闭任何流和清理任何被异常所打开的对象。以下的代码关闭了StreamReader对象,无论一个异常是否发生StreamReader sr = new StreamReader("text.txt");
try
{
Console.WriteLine(sr.ReadToEnd());
}
catch (Exception ex)
{
// If there are any problems reading the file, display an error message
Console.WriteLine("Error reading file: " + ex.Message);
}
finally
{
// Close the StreamReader, whether or not an exception occurred
sr.Close();
}
?
注意:StreamReader的声明移到了Try块之外。这时必须的,因为Finally块不能访问Try块内声明的变量。这样的设计是明智的,因为可能会引发的异常使Try块内声明的变量可能没有被执行。使用Try/Catch/Finally?块可以捕捉StreamReader声明时或之后的异常。当错误发生时,强健的错误处理提升了用户的经验并且简化了更改错误的过程。
?
异常处理引发了一个轻量级的性能损耗。
?