微软.NET FRAMEWORK 2.0 程序设计

来源:互联网 发布:java rpc 编辑:程序博客网 时间:2024/06/03 15:57

**************************

***第一章FRAMEWORK基础*****

**************************
.NET Framework 是 Microsoft Windows 组件旨在支持下一代应用程序和服务的整数。 .NET 很多基础知

识框架将开发人员曾在其他面向对象的开发环境中所熟悉 ; 但是,.NET Framework 还包括到甚至最有

经验的开发人员的新的很多新的元素。本章概述了包括这本书中的每个其他章所需的知识的.NET

Framework 编程。
--------------------------------------------------------------------------------
注意:
如果您曾与版本的.NET Framework 2.0 版之前公布,大部分这将熟悉。 但是,.NET Framework 2.0 版

包括若干新功能: 泛型、 分部类和类型转发 (所有 “ 构造类 ” 第 3 课中所述)。
--------------------------------------------------------------------------------
本章中的考试目标:
一:通过使用.NET Framework 2.0 系统类型管理.NET Framework 应用程序中的数据。 (请参阅 System

命名空间)
1、值类型
2、引用类型
3、属性
4、泛型类型
5、异常类
6、装箱和拆箱
7、TypeForwardedToAttribute 类
二:实现.NET Framework 接口要遵守标准的组件。 (请参阅 System 命名空间)
1、IComparable 接口
2、IDisposable 接口
3、IConvertible接口
4、ICloneable  接口
5、IEquatable  接口
6、IFormattable接口
三:控制通过使用事件和委托的.NET Framework 应用程序组件之间的交互。 (请参阅 System 命名空间


1、委托类
2、事件类
3、EventHandler 委托
在开始之前
    本书假定您使用.NET Framework 1.0、 NET Framework 1.1 和 NET Framework 2.0 有最少两至三年

的经验开发基于 Web 的 Microsoft 基于 Windows 或分布式应用程序。 候选人应把有工作知识的

Microsoft Visual Studio 2005。 在开始之前您应该熟悉 Microsoft Visual Basic 或 C#,属熟悉下列

任务:
1、在 Visual Studio 使用 Visual Basic 或 C# 中创建控制台或 WindowsForms 应用程序。
2、将命名空间和系统类库的引用添加到项目。
3、在 Visual Studio 中运行项目设置断点、 逐句通过代码,观看变量的值。

====================
====第 1 课: 使用值类型===

====================
    在.NET Framework 主要数字和布尔的类型中,最简单类型是值类型。 值类型是包含其数据直接而不

是包含对其他地方存储在内存中数据的引用的变量。 值类型的实例存储在内存调用堆栈的运行库可以创

建、 读取、 更新,和与最低开销快速删除它们的区域。
--------------------------------------------------------------------------------
关于参考类型更多信息
有关引用类型相关的信息请参阅第 2 课。
--------------------------------------------------------------------------------
有三种一般值类型:
1、布尔类型
2、用户自定义类型
3、枚举类型
每一种类型都是从 System.Value 基类型派生的。 以下各节介绍如何使用这些不同类型。
本课程后您将能够:
1、选择最有效的内置值类型
2、声明值类型
3、创建你自己的类型
4、使用枚举类型
估计课时间: 30 分钟
内置值类型
内置类型是生成其他类型的基类型。所有内置数值类型都是值类型。您选择基于您希望使用的值的大小和

精度您需要的级别为数值类型。
    这些数值类型被如此频繁使用 Visual Basic 和 C# 为它们定义别名。 使用别名等同于使用完整的

类型名称,因此大多数程序员使用较短的别名。 除了,数值类型列出在表 1-2 中,非数值数据类型也是

值类型。在框架有近 300 多个值类型,基本满足了需要。值类型变量之间分配时,数据和变量在不同的

堆栈上。此行为是不同的和第 2 课中讨论的引用类型。
即使值类型通常表示简单值,他们仍作为对象。 换句话说,您可以对它们调用方法。其实,它是通用的

显示值作为文本时使用 ToString 方法。 从基本 System.Object 类型重写 ToString
注意 对象基类
    在.NET Framework 中所有类型都派生自 System.Object。 这种关系有助于建立使用整个 Framework

通用类型系统。
如何声明值类型
如果要使用一个类型,你必须生命一个该类型的实例。值类型有一个隐式的构造函数,因此它们自动声明

实例化类型;不必像类那样包含new关键字。构造函数分配默认值通常是NULL或0到新实例。但下面的代码

所示应始终显式在声明变量块初始化内:
bool b = false;
如果要能够确定是否尚未分配一个值,声明为空的变量。例如如果您存储数据是从窗体和用户没有问题并

没有回答问题,您应存储空值。 以下代码允许为是 true、 false,或其他的布尔变量:
// C#
Nullable<bool> b = null;
//简写表示法
bool? b = null;
作为空启用 HasValue 和 Value 成员声明变量。 有值用于检测已设置一个值:
// C#
if (b.HasValue)Console.WriteLine("b is {0}.", b.Value);
else Console.WriteLine("b is not set.");
怎样创建用户自定义类型
   用户定义的类型也称为结构或只是结构,用于创建它们在语言关键字后。与其它值用户定义类型的实

例存储在堆栈上,并且他们直接包含其数据。 在大多数其它方面几乎与类的行为相同。
   结构是一个复合使其易于使用的相关数据的其他类型。最简单的例子是 System.Drawing.Point 其中

包含定义水平和垂直坐标的一点的 X 和 Y 整数属性。 Point 结构简化使用坐标通过提供构造函数和此

处显示的成员:
// C# - Requires reference to System.Drawing
// Create point
System.Drawing.Point p = new System.Drawing.Point(20, 30);
// Move point diagonally
p.Offset(-1, -1);
Console.WriteLine("Point X {0}, Y {1}", p.X, p.Y);
    通过使用 Visual Basic 中的结构关键字或 struct 关键字在 C# 中的定义您自己的结构。 例如以

下代码创建一个类型,由构造函数设置的最小和最大值之间循环通过一套整数:
// C#
struct Cycle
{
  // Private fields
  int _val, _min, _max;
  // Constructor
  public Cycle(int min, int max)
  {
    _val = min;
    _min = min;
    _max = max;
  }
  public int Value
  {
    get { return _val; }
    set
       {
        if (value > _max)
        _val = _min;
        else
       {
    if (value < _min)
    _val = _max;
    else
    _val = value;
   }
  }
}
   public override string ToString()
   {
     return Value.ToString();
   }
   public int ToInteger()
   {
    return Value;
   }
   // Operators (new in .NET 2.0)
   public static Cycle operator +(Cycle arg1, int arg2)
   {
     arg1.Value += arg2;
     return arg1;
   }
   public static Cycle operator -(Cycle arg1, int arg2)
   {
    arg1.Value -= arg2;
    return arg1;
   }
}
您可以使用此结构来表示项目,如程度的旋转或宿舍一足球场游戏一个固定范围内重复,如下所示:
// C#
Cycle degrees = new Cycle(0, 359);
Cycle quarters = new Cycle(1, 4);
for (int i = 0; i <= 8; i++)
{
degrees += 90; quarters += 1;
Console.WriteLine("degrees = {0}, quarters = {1}", degrees, quarters);
}
周期示例可以轻松地转换至为引用类型的值类型通过类更改结构/结构关键字。 如果进行的更改则应周期

类的实例会拨在托管堆上而不是 12 字节上堆栈 (为每个专用整数字段的 4 个字节) 和两个变量结果

在两个变量指向同一个实例之间的分配。
类似功能时结构是通常比类更有效。
    您应定义一个结构而不是如果该类型将执行的类作为值更好地键入比引用类型。 具体来说,结构类

型应满足所有这些准则:
1、逻辑上表示单个值
2、有一个实例大小小于 16 字节
3、将创建后不会更改
4、不会被转换为引用类型
怎样创建枚举类型
    枚举是具有固定值的相关的符号。 使用枚举来使用您的类的开发人员提供的选择列表。 例如以下枚

举包含一组的标题:
// C#
enum Titles : int { Mr, Ms, Mrs, Dr };
如果您创建了标题类型实例的 Visual Studio 将显示可用值的列表时将一个值分配给变量,。 但变量的

值是一个整数,是易于输出而它的值如下所示的符号名称在这里:
// C#
Titles t = Titles.Dr;
Console.WriteLine("{0}.", t); // 显示 "Dr."
    枚举旨在简化编码和改善代码可读性,使您可以使用有意义的符号而不是简单的数值。 时使用您的

类型的开发人员必须从一组有限的一个值,选择的选择,请使用枚举。
声明和使用值类型
以下练习演示如何创建和使用结构以及如何创建枚举。 如果遇到完成一次,已完成的问题项目有代码文

件夹中附带 CD 上:
行使 1: 创建一个结构
在本练习您将创建一个简单的结构与多个公共成员。
1.使用 Visual Studio 创建新的控制台应用程序项目。 该项目命名为CreateStruct。
2.创建新的结构名为 Person,按下面的代码演示。
// C#
struct Person
{
}
3.在Person结构里生命三个公共成员
❑ firstName (a String)
❑ lastName (a String)
❑ age (an Integer)
以下代码演示了这一点:
// C#
public string firstName;
public string lastName;
public int age;
创建一个构造函数定义所有三个成员变量,如下面的代码演示
// C#
public Person(string _firstName, string _lastName, int _age)
{
  firstName = _firstName;
  lastName = _lastName;
  age = _age;
}
.重写 ToString 方法以显示Person firstName、 lastName和age。 以下代码演示了这一点:
public override string ToString()
{
   return firstName + " " + lastName + ", age " + age;
}
6.在控制台应用程序主方法编写代码以创建结构的一个实例,并将该实例传递给该 Console.WriteLine

方法 下面的代码演示了:
// C#
Person p = new Person("Tony", "Allen", 32);
Console.WriteLine(p);
7.运行控制台应用程序以验证正确工作。
行使 2: 将枚举添加到一个结构
在本练习您将扩展运动 1 中创建加入枚举结构。
1.打开计划 1 中创建的项目。
2.声明人结构中的新枚举。 名称,枚举Genders并指定两个可能值: 男性和女。 下面的代码示例
演示了这种情况:
// C#
public enum Genders : int { Male, Female };
3.添加公共成员的键入Genders,和修改Person构造函数接受的性别实例。 以下代码演示了这一点:
// C#
public string firstName;
public string lastName;
public int age;
public Genders gender;
public Person(string _firstName, string _lastName, int _age, Genders _gender)
{
  firstName = _firstName;
  lastName = _lastName;
  age = _age;
  gender = _gender;
}
修改Person.ToString 方法
// C#
public override string ToString()
{
  return firstName + " " + lastName + " (" + gender + "), age " + age;
}
5.按下面的代码示例演示修改主代码以适当构造的的 Person 类实例:
// C#
static void Main(string[] args)
{
  Person p = new Person("Tony", "Allen", 32, Person.Genders.Male);
  Console.WriteLine(p.ToString());
}
6.运行控制台应用程序以验证正确工作。
本课摘要
1、.NET Framework 包括大量的内置类型可以直接使用或使用构建自己的自定义类型。
2、值类型直接包含其数据提供优良的性能。 值类型却限于存储很小块的数据的类型。 在 N 所有值类型

都的 16 个字节或较短。
3、可创建用户定义类型存储多个值和方法。 在面向对象的开发环境中的应用程序逻辑一大部分将存储在

用户定义类型。
4、枚举提供符号为一组值,从而改善代码可读性。
课后习题
您可以使用以下问题测试您的有关知识中第 1 课 “ 使用值类型 ”。 如果您想检讨他们以电子形式,

附带 CD 上也有问题。

===============================
========第 2 课: 使用公共引用类型=====
===============================
在.NET Framework 中的大多数类型是引用类型。引用类型提供一个极大的灵活性,并传递他们时,他们提供出色的性能方法。以下各节讨论公共内置类引入引用类型。第 4,课 介绍创建类、 接口和委托。
本课程后您将能够:
1、分别解释值类型和引用类型。
2、描述如何值类型和引用类型不同时分配值。
3、列出内置引用类型。
4、描述当您应使用 StringBuilder 类型。
5、创建和排序数组。
6、打开、读、写和关闭文件
检测到异常发生和响应异常时。
预计需要40分钟
引用类型是什么?
引用类型将他们的数据也称为一个指针的地址存储在堆栈上。实际地址指向的数据存储在内存调用堆的区域中。运行库管理,由通过一个称为垃圾回收的过程堆使用的内存。 垃圾回收恢复内存定期根据需要的处置不再引用的项目。
垃圾收集的最好方法
仅在需要时或触发对 GC.Collect 的调用时,会发生垃圾回收。 自动垃圾回收为实例最多有短暂除外分配的应用程序开头的应用程序进行了优化。 以下的设计模式会最好的性能
比较引用和值类型的行为
将引用类型表示的数据而不是数据本身地址,因为将一个引用变量分配给另一个不是数据复制。 而,只将引用变量分配给另一个实例创建引用它是指相同的内存位置在堆上了原始变量的第二个副本。
请考虑下面的简单结构声明:
struct Numbers
{
  public int val;
  public Numbers(int _val)
  { val = _val; }
  public override string ToString()
  { return val.ToString(); }
}
现在考虑以下代码创建数字结构的实例,将复制到另一个实例的结构修改两个值,并显示结果。
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 ” 因为一个结构是种值,并复制值类型结果在两个不同的值。
但是,如果您到类从一个结构更改数字类型声明相同的应用程序会显示 “ n1 = 3 n2 = 3 ”。 到类从结构更改数字会导致要引用键入而不是值类型。 在修改引用类型时您将修改该引用类型的所有副本。
内置引用类型
在.NET Framework中有大约2500个内置引用类。不是从 System.ValueType 派生的是引用类型,包括这些 2500 或因此内置引用类型。表 1-3 列出了从的很多其他引用类型派生,最常用的类型。
Table 1-3常见的引用类型
System.Object:Object 类型是 Framework 中的最通用类型。您可以将任何类型转换为 System.Object,并可以依靠从此类型继承的 ToString、 GetType 和 Equals 成员任何类型。
System.String:文本数据。
System.Text.StringBuilder:动态文本数据。
System.Array:数组的数据。 这是所有数组的基类。数组声明使用特定于语言的数组语法。
System.IO.Stream:文件、 设备,和网络 I/O 的缓冲区。 这是一个抽象基类 ; 特定任务类均派生从流。
System.Exception:处理系统和应用程序定义的异常。特定任务的异常继承此类型。
字符串和字符串生成器
类型不仅仅数据的容器,他们也提供操作通过其成员的数据的方法。 System.String 提供一组的成员
使用文本。 例如下面的代码并快速搜索,并替换:
// C#
string s = "this is some text to search";
s = s.Replace("search", "replace");
Console.WriteLine(s);
类型为 System.String 的字符串是不可变在.NET 中的。 这意味着字符串的任何更改使运行库创建一个新的字符串和放弃旧。 发生这种情况,并且多程序员可能诧异下面的代码分配内存中的四个新字符串:
// C#
string s;
s = "wombat"; // "wombat"
s += " kangaroo"; // "wombat kangaroo"
s += " wallaby"; // "wombat kangaroo wallaby"
s += " koala"; // "wombat kangaroo wallaby koala"
Console.WriteLine(s)
    只有最后一个被引用,其它三个都被自动垃圾回收。避免这些类型的临时字符串有助于避免不必要的垃圾集合,可以提高性能。 有多种,避免临时字符串方法:
1、使用 String 类的 String.Concat、 加入或格式方法在单个语句中加入多个项目。
2、使用StringBuilder创建一个动态文本。
StringBuilder 解决方案是最灵活的因为它可以跨越多个语句。默认的构造函数会根据需要创建一个长度为16字节的缓冲区。您如果您喜欢可以指定的初始大小和最大大小。下面的代码演示使用StringBuilder:
// C#
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 类的另一个细微但重要功能是它重写从 System.Object 的运算符。 表 1-4 列出操作 String 类重写。
Table 1-4 String Operators
加法   +  将两个文本连接创建一个新的文本对象。
等于   == 如果两个文本串相同返回真值,不同则返回假。
不等于 != 相等运算符的逆。
赋值   =  将一个字符串的内容复制到一个新。 这将导致像值类型的字符串,即使它们实施作为引用类型。
如何创建和排序数组
作为声明的一部分使用括号 (在 Visual Basic 中) 或平方米的大括号 (在 C# 中) 声明数组。正如 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.Security.Cryptography 命名空间中找到 System.Network.Sockets 命名空间和加密的流。
Table 1-5 Common Stream Types
FileStream:创建一个基本流来读写文件。
MemoryStream:在内存中创建一个基本流进行读写。
StreamReader: 从流里读取数据。
StreamWriter: 从流里写数据。
最简单的流类是 StreamReader 和 StreamWriter,使您能够读取和写入文本文件。作为构造函数使您可以用一行代码打开一个文件的部分,可以传递一个文件名。您已处理一个文件后调用 Close 方法,以便该文件并不保持锁定。下面的代码需要 System.IO 命名空间演示如何写入和读取一个文本文件:
// C#
// 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();
更多信息流
关于更多的数据流在第二章的, “Input/Output (I/O).”
如何引发和捕获异常
异常是中断正常执行的程序集的意外的事件。例如如果您的程序集从可移动磁盘读取一个大型的文本文件,并且用户删除磁盘运行库将引发异常。这个意思是无法继续运行。
异常应该永远不会导致程序集完全失败。而,您应计划发生、 捕捉他们,和响应事件的异常。在前面的示例可以通知用户该文件没有,然后等候进一步从用户的说明。以下简化的需要代码,System.IO 命名空间演示了这一点:
// C#
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 类非常有用,并包含一条错误消息和其他应用程序数据。除了,异常基类,Framework 定义了数以百计的描述不同类型的事件从所有派生的异常类 System.SystemException。此外,您可以定义您自己的异常时您描述在更详细的标准异常类允许通过从 System.ApplicationException 派生以外一项活动需要。
有多个异常类允许您以不同的方式回应不同类型的错误。运行库将执行只第一个 Catch 块与一个匹配的异常类型,因此Catch顺序最好是从最明确错误到不明确的错误,逐渐浅化。下面的代码示例显示错误、 出错足够的权限和任何其他类型的可能发生的错误找不到一个文件的不同的错误消息:
//c#
try
{
  StreamReader sr = New StreamReader("text.txt");
  Console.WriteLine(sr.ReadToEnd);
}
catch(System.IO.FileNotFoundException ex)
{
  Console.WriteLine("The file could not be found.");
}
catch(System.UnauthorizedAccessException ex)
{
  Console.WriteLine("You do not have sufficient permissions.");
}
catch(Exception ex)
{
  Console.WriteLine("Error reading file: " + ex.Message);
}
此过程有时称为筛选的异常。 异常处理也支持一个 Finally 块。在 Finally 块运行 Try 块和任何 Catch 块完成执行后, 是否引发异常。因此如果一个异常发生,您应使用一个 Finally 块,关闭任何流或清理可能将保留任何其他打开对象。不论是否发生异常的对象下面的代码示例关闭 StreamReader:
// C#
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();
}
   请注意在前面的示例在 Try 块外提出 StreamReader 声明。这是必要的因为在 Finally 块无法访问在 Try 块中声明的变量。这使意义,因为取决于异常的发生位置在 Try 块中的变量声明可能没有执行。若要捕捉异常发生期间及之后 StreamReader 声明,使用嵌套 Try/Catch/Finally 块。
   通常,除为简单的变量声明的所有代码应都出现在 Try 块中。可靠的错误处理可改进用户体验时问题发生,大大简化了调试问题。但是,异常处理并会导致稍许的性能下降。节省空间和集中的特定主题,
这本书内的示例代码通常不会包括异常处理。
实验室: 使用引用类型
以下练习加强引用类型、 字符串和异常的知识。如果您遇到完成工作的问题,已完成的项目可用代码文件夹中附带 CD 上。
行使 1: 标识作为值或引用类型
在这次您将编写一个控制台应用程序,显示是否对象是值类型还是引用类型。
1.使用 Visual Studio 创建新的控制台应用程序项目。 该项目名称List-Value-Types.
2.创建以下类的实例:
❑ SByte
❑ Byte
❑ Int16
❑ Int32
❑ Int64
❑ String
❑ Exception
以下代码演示了这一点:
// C#
SByte a = 0;
Byte b = 0;
Int16 c = 0;
Int32 d = 0;
Int64 e = 0;
string s = "";
Exception ex = new Exception();
3.添加各个实例到一个新的对象数组如下下面的代码演示:
// C#
object[] types = { a, b, c, d, e, s, ex };
4.在一个 Foreach 循环循环检查 object.GetType ().IsValueType 属性确定类型是否是值类型。 显示每个键入名称和是否值类型或引用键入,下面的代码演示:
// C#
foreach ( object o in types )
{
  string type;
  if (o.GetType().IsValueType)
  type = "Value type";
  else
  type = "Reference Type";
  Console.WriteLine("{0}: {1}", o.GetType(), type );
}
5.运行控制台应用程序并确认每个类型匹配您了解。
行使 2: 使用字符串和数组
在这次您将编写一个排序字符串的函数
1.使用 Visual Studio 创建新的控制台应用程序项目。 该项目名称SortString。
2.定义一个字符串。 然后,使用 String.Split 方法字符串分成单词的数组。 以下代码演示了这一点:
// C#
string s = "Microsoft .NET Framework 2.0 Application Development Foundation";
string[] sa = s.Split(' ');
3.按下面的代码演示调用 Array.Sort 方法来对单词,数组进行排序:
// C#
Array.Sort(sa);
4.调用 String.Join 方法将数组的单词回转换单个字符串,然后将字符串写入控制台。 下面的代码示例演示此:
// C#
s = string.Join(" ", sa);
Console.WriteLine(s);
5.运行控制台应用程序并确认正常工作。
行使 3: 使用流和异常
请考虑在其中一个同事编写一个简单的 Windows 窗体应用程序,以查看文本文件的情况。 但是,用户投诉是非常多。 如果用户输入错误文件名或如果文件不可用因任何理由,应用程序失败并出现未处理的异常错误。 如果文件不可用您必须添加异常处理应用程序向用户显示友好错误信息。
将 1 Chapter01/Lesson2 ViewFile 文件夹从本书 CD 复制到您的硬盘上并打开 C# 版本或 Visual Basic.NET 版本的ViewFile 项目。
2.当用户尝试查看文件会发生异常。因此,编辑代码运行 showButton.Click 事件。添加代码以捕获任何类型的异常发生,并在向用户一个对话框中显示错误信息。如果发生异常的 TextReader 对象初始化后,您应关闭它是否一个发生异常。您需要两个嵌套的 Try 块: 一个 TextReader 在初始化期间捕捉异常,第二个一个以读取该文件时捕获异常。 下面的代码示例演示此:
// C#
try
{
  TextReader tr = new StreamReader(locationTextBox.Text);
  try
  { displayTextBox.Text = tr.ReadToEnd(); }
  catch (Exception ex)
  { MessageBox.Show(ex.Message); }
  finally
  { tr.Close(); }
}
catch (Exception ex)
{ MessageBox.Show(ex.Message); }
3.运行应用程序。 先验证成功可以显示一个文本文件。 然后提供一个无效的文件名,并验证提供文件名无效时出现一个消息框。
4.接下来添加重载的异常处理捕捉 System.IO.FileNotFoundException 和 System.UnauthorizedAccessException。 下面的代码示例演示此:
// C#
try
{
  TextReader tr = new StreamReader(locationTextBox.Text);
  try
  { displayTextBox.Text = tr.ReadToEnd(); }
  catch (Exception ex)
  { MessageBox.Show(ex.Message); }
  finally
  { tr.Close(); }
}
catch (System.IO.FileNotFoundException ex)
{ MessageBox.Show("Sorry, the file does not exist."); }
catch (System.UnauthorizedAccessException ex)
{ MessageBox.Show("Sorry, you lack sufficient privileges."); }
catch (Exception ex)
{ MessageBox.Show(ex.Message); }
5.再次运行应用程序,并验证它提供新的错误信息,是否提供了一个无效的文件名。
本课摘要
■ 引用类型包含数据,而非实际数据地址。
■ 如果您复制值类型,被创建第二个副本的值。 当您复制引用类型时,被复制只指针。 因此,如果您复制的引用键入,然后修改该副本副本和原始变量被更改。
■ 的.NET Framework 包括大量的内置引用类型可以直接使用或使用构建自己的自定义类型。
■ 字符串是不可变的 ; 使用 StringBuilder 类来动态创建一个字符串。
■ 使用流读取和写入文件、 内存和网络。
■ 用于 Catch 子句在 Try 块中的筛选由类型的异常。 关闭并释放 nonmemory 资源中,最后条 Try 块。

===================================
========第三课  创建类   =================
===================================
在面向对象语言中大部份的工作应对象中执行。所有但最简单的应用程序需要兴建一或多个自定义类每多个属性和用于执行任务的方法与
该对象。这一课讨论如何创建自定义类。
本课程后您将能够:
■描述并使用继承。
■描述和使用接口。
■描述并使用分部类。
■创建一个泛型类型和使用内置的泛型类型。
■响应,并引发事件
■添加属性以描述程序集和方法
■移动到另一个从一个类库类型使用类型转发。
预计课时40分钟
什么是继承?
.NET Framework 数以千计的类,并每个类有许多不同的方法和属性。跟踪所有这些类和成员的不可能如果.NET Framework 不实现非常一致。例如每个类具有一个 ToString 方法,执行完全相同的任务 — 类的一个实例转换为字符串。同样地,许多类支持同一操作如比较两个实例相等的类。
此一致性是可能因为继承和接口 (如下一节所述)。从现有的类中使用继承来创建新的类。如您将了解第 6 章 “ 图形,” Bitmap 类从 Image 类继承,并通过添加功能扩展中。因此,您可以使用 Bitmap 类的实例您会使用 Image 类的实例以相同方式。但是,Bitmap 类提供了其他方法,使您进行更多的图片处理和操作。您可以轻松创建一个自定义异常处理类通过继承 System.Application-异常,如下所示:
// C#
class DerivedException : System.ApplicationException
{
  public override string Message
  {
    get { return "An error occurred in the application."; }
  }
}
您可以引发和捕捉新异常,因为自定义类继承其基类的行为,如下所示:
// C#
try
{
  throw new DerivedException();
}
catch (DerivedException ex)
{
  Console.WriteLine("Source: {0}, Error: {1}", ex.Source, ex.Message);
}
从 System.Application 异常继承自定义异常不仅支持该 throw/catch 行为,但它还包括源成员 (以及其他) 的通知。
继承的另一个好处是能够互换使用派生的类。例如有 System.Drawing.Brush 从基类继承的五个类:HatchBrush, LinearGradientBrush, PathGradientBrush, SolidBrush,和 TextureBrush。Graphics.DrawRectangle 方法需要 Brush 对象作为其参数之一 ;但是,您会将永远不会通过 Brush 基类转换为 Graphics.DrawRectangle。 而且,您将传递派生类之一。 因为每个从 Brush 类派生他们,aphics.DrawRectangle 方法可以接受任何人。 同样,如果您要创建一个从 Brush 类派生的自定义类,您可能还将该类传递给 Graphics.DrawRectangle。
什么是接口?
也称为合约,接口定义成员的实现接口的所有类必须都提供的常见的集。例如 IComparable 接口定义 CompareTo 方法,使两个实例相等比较类。实现 IComparable 接口接口的所有类创建自定义-还是兴建,可以进行都比较相等。
IDisposable 是类的提供单个方法 Dispose 以便创建您释放该实例占用了任何资源的实例的程序集的接口。若要提供一个实现 IDisposable 接口使用 Visual Studio 2005 类请按照下列步骤操作:
1.创建类声明。 例如:
// C#
class BigClass
{
}
2.添加接口声明。 例如:
// C#
class BigClass : IDisposable
{
}
3.如果您使用 Visual Basic,Visual Studio 应该自动生成方法声明为每个所需的方法。 如果它不删除该
实现命令,然后重试 ;Visual Studio 可能仍在启动。 如果您使用 C#,右键单击接口声明,单击实现接口然后再单击实现接口,图 1-1 所示。
4.为每个接口的方法编写代码。 在此示例中,您会编写在 Dispose 方法释放已分配任何资源中的代码。
表 1-6 列表,通常用于在.NET Framework 中的接口。
IComparable:实施由其值可以进行排序的类型 ;例如,数字和字符串类。 IComparable 是所需排序。
IDisposable:定义方法手动释放的对象。 这接口是重要的消耗的大型对象资源或锁定访问的对象如数据库的资源。
IConvertible:启用要转换为一个基础类如 Boolean Byte,Double,键入或 String。
ICloneable:复制的对象的支持。
IEquatable:允许您与平等类的实例进行比较。 例如如果要实现此接口,可以说 “ 如果 (a = = b) ”。
IFormattable:使您能够对象的值转换为特殊格式的字符串。 这提供了,比基本的 ToString 方法更灵活
您可以创建您自己接口。这很有用如果您要创建多个自定义类同样的行为,可互换使用。 例如下面的代码定义包含三个成员的接口:
// C#
interface IMessage
{
  // Send the message. Returns True is success, False otherwise.
  bool Send();
  // The message to send.
  string Message { get; set; }
  // The Address to send to.
  string Address { get; set; }
}
如果您在一个新类中实现该接口,Visual Studio 将为接口成员生成下列模板:
class EmailMessage : IMessage
{
  public bool Send()
  {
    throw new Exception("The method or operation is not implemented.");
  }
  public string Message
  {
    get
   {
     throw new Exception("The method or operation is not implemented.");
   }
    set
   {
     throw new Exception("The method or operation is not implemented.");
   }
  }
  public string Address
  {
    get
    {
     throw new Exception("The method or operation is not implemented.");
    }
    set
    {
     throw new Exception("The method or operation is not implemented.");
    }
   }
}
如果您创建自定义类,并且以后决定会很有用具有相同的成员的多个类,VisualStudio 有接口摘录自定义类的快捷方式。 只需请按照这些步骤操作:
1.右击 Visual Studio 2005 中的类。
2.单击重构,然后单击提取接口。
3.指定接口名称,选择公共成员,相应窗体,接口,然后单击确定。
类可以实现多个接口。 因此,一个类可以实现 IComparable 和 IDisposable 接口。
什么是分部类?
注意 .NET 2.0 分布类是.net2.0新概念
    分部类允许您跨多个源文件拆分类定义。此方法的好处是它隐藏类定义的详细信息,以便派生的类可以专注于更重要的部分。
    Windows 窗体类是一个内置的分部类的示例。在 Visual Studio 2003 中和较早前,窗体类包含由窗体设计器生成代码。现在在一个名为 form.Designer.vb 或 form.Designer.cs 的分部类中是隐藏的代码。
在 Visual Basic 中必须选择在 SolutionExplorer@@ 可以查看分部类文件显示所有文件。在 C#,视图默认启用的。分部类不是考试目标的一部分,但您需要知道它们以便您可以找到该窗体当您创建一个新的 Windows 窗体的设计器代码的时候。
什么是泛型?What Are Generics?
泛型是指在类或者方法包含类型参数,从而可以使用同一个类或方法能够作用于多种不同的类型。而不是指定类型的参数或成员类,您可以使用您的类型指定它的代码。这允许使用者代码定制您自己的具体需求的类型。
考试提示:由于泛型是.net2.0新添的内容,因此在考试中关于泛型会出现大量的试题。
.NET Framework 2.0 版中,System.Collections 包括几个泛型类。泛型命名空间包括 Dictionary、 Queue、 SortedDictionary 和 SortedList。这些类工作同样非泛型接口 System.Collections,但它们提供改进的性能和类型安全。
更多信息 一般集合
.NET Framework 2.0 版包括提供了 System.Collections.Generic 命名空间提供改进的性能标准集合的内置集合。 有关更多信息请参阅第 4 “ 集合和泛型 ”。
为什么要用泛型呢?
1.0 和 1.1 版的.NET Framework 不支持泛型。而开发人员 Object 类用于参数和成员,并会转换至 Object 类的其他类。 泛型提供通过使用 Object 类的两个重要优点:
■ 降低运行时错误:您强制转换时,编译器无法检测类型错误至 Object 类。例如如果转换一个字符串,Object 类
然后尝试强制转换为整数的对象,编译器不会捕获错误。 而,运行库将引发异常。 使用泛型允许,
编译器将捕捉此类型的错误之前程序运行。 此外,您可以指定约束,以限制在一个泛型中使用该类启用编译器检测到不兼容的类型。
■ 改善性能:需要装箱和取消装箱 (稍后解释在 Lesson 4 “ 转换 Between Types ”) 的 steals 处理器时间并降低性能。 使用泛型 doesn’t 需要转换或装箱提高了运行时性能。
怎样创建泛型类?
首先,研究以下类。 类对象和第代执行完全相同任务,但是对象使用 Object 类来启用任何类型必须通过第代使用泛型时:
// C#
class Obj
{
  public Object t;
  public Object u;
  public Obj(Object _t, Object _u)
  {
   t = _t;
   u = _u;
  }
}
class Gen<T, U>
{
  public T t;
  public U u;
  public Gen(T _t, U _u)
  {
   t = _t;
   u = _u;
  }
}
   你可以看到,对象类有两个对象成员类型,这个gen类有两个成员分别是T类型和U类型。耗费大量代码来确定T和U的类型,T和U可以是字符串、整形、一个自定义类或任何组合。
创建泛型类还有一个重大限制:泛型代码如果才会有效它将编译为每个可能兴建的泛型实例是否一个 int 一个字符串或任何其他类。因此,您可以在您的类调用 ToString 或 GetHashCode 方法,但您不能使用+,或 > 运算符。这些相同的限制不适用于占用代码因为耗费大量代码已声明为泛型类型。
如何使用泛型类型
当您使用泛型类型时,您必须指定使用任何泛型的类型。考虑以下控制台应用程序代码,它会使用GEN和对象类:
// C#
// Add two strings using the Obj class
Obj oa = new Obj("Hello, ", "World!");
Console.WriteLine((string)oa.t + (string)oa.u);
// Add two strings using the Gen class
Gen<string, string> ga = new Gen<string, string>("Hello, ", "World!");
Console.WriteLine(ga.t + ga.u);
// Add a double and an int using the Obj class
Obj ob = new Obj(10.125, 2005);
Console.WriteLine((double)ob.t + (int)ob.u);
// Add a double and an int using the Gen class
Gen<double, int> gb = new Gen<double, int>(10.125, 2005);
Console.WriteLine(gb.t + gb.u);
如果您在一个控制台应用程序中运行该代码,obj和gen类产生完全相同的结果。但是,使用gen类的代码实际可更快,因为它不需要装箱和取消装箱至 Object 类。另外,开发人员必须使用gen类很容易。首先,开发人员会不要手动从 Object 类转换为相应的类型。第二,在编译时 (而不是在运行时),会被捕获类型错误。若要演示的好处,请考虑下面的代码包含一个错误.
// C#
// Add a double and an int using the Gen class
Gen<double, int> gc = new Gen<double, int>(10.125, 2005);
Console.WriteLine(gc.t + gc.u);
// Add a double and an int using the Obj class
Obj oc = new Obj(10.125, 2005);
Console.WriteLine((int)oc.t + (int)oc.u);
最后一行在该代码示例包含一个错误 — oc.t 值转换到一个 double 而不是一个 int。可惜,编译器不能捕捉该错误。当而,在 C# 中运行时异常时将引发运行时尝试转换为 int 值一个 double。 更易于修复的错误,编译器捕获和很难检测并修复一个运行时错误,因此泛型类提供一个明确的好处。
如何使用约束
如果您可以只编写会编译的任何类的代码,因为您会限于对象基类的功能,泛型会极为有限。若要克服此限制,使用约束来放占用代码可以替换为您泛型类型的要求。
泛型支持四种类型的约束:
■ 接口:允许仅实现特定接口使用您泛型的类型。
■ 基类:允许仅匹配或使用您泛型特定从基类继承的类型。
■构造函数:需要使用您泛型实现无参数构造函数的类型。
■引用或值类型:需要使用您泛型来引用或值类型的类型。
在 Visual Basic 中使用 As 子句或 where 子句 C# 约束应用于泛型。 例如以下泛型类只能是实现 IComparable 接口:
// C#
class CompGen<T>
  where T : IComparable
{
  public T t1;
  public T t2;
  public CompGen(T _t1, T _t2)
  {
   t1 = _t1;
   t2 = _t2;
  }
  public T Max()
  {
   if (t2.CompareTo(t1) < 0)
   return t1;
   else
   return t2;
   }
}
上述类将正确编译。 但是,如果您删除 where 子句,编译器将返回错误指示不包含泛型类型 T 中的CompareTo 定义。通过约束泛型实现的类
IComparable,您保证 CompareTo 方法将始终可用。
事件
大多数项目是非线性的。在 Windows 窗体应用程序中您可能要等待用户可以单击一个按钮或按一个键,然后响应该事件。在服务器应用程序中您可能要等待传入的网络请求。 这些功能是由.NET Framework事件提供的以下各节所述。
什么是事件?
事件是操作的一个由对象发送信号发生的消息。操作可能是由用户交互如单击鼠标或导致的一些其他程序逻辑触发。引发事件的对象称为事件发件人。捕获事件并响应,该对象称为事件接收器。
在事件通信中事件发件人类不知道哪些对象或方法将接收 (句柄) 它引发的事件。需要的是中介(或指针类似的机制) 源和接收方之间。.NET Framework 定义一个特别 (Delegate) 的类型提供的函数指针的功能。
委托是什么?
委托是一个类,可以保存对方法的引用。 与其他类委托类具有一个签名,它可以保存只对与其签名匹配的方法的引用。委托是因此等效于一个类型安全函数指针或一个回调。委托有其它用途,在此讨论的重点委托的事件处理功能。委托声明是足以定义一个委托类。声明供应该委托的签名和公共语言运行库提供实现。下面的示例演示一个事件委托声明:
// C#
public delegate void AlarmEventHandler(object sender, EventArgs e);
标准的事件处理程序委托签名定义并不返回一个值是其第一个参数是 Object 类型,引用实例引发该事件,并且从类型 EventArgs 派生其第二个参数,保留事件数据的方法。如果该事件不会生成事件数据,第二个参数是 EventArgs 的简单实例。否则,第二个参数是一个自定义的类型从 EventArgs 派生和提供任何字段或保留事件数据所需的属性。
EventHandler 是一个预定义的委托,明确表示不生成数据的事件的事件处理程序方法。如果您的事件生成数据,您必须提供您自己的自定义事件数据类型,或者创建一个委托类型的第二个参数是您自定义的类型或您必须使用泛型的 EventHandler 委托类并替换为泛型类型参数自定义类型。
若要将事件与将处理该事件方法,将委托的一个实例添加到事件。每次发生该事件时,调用事件处理程序除非您删除该委托。
如何响应事件
您必须执行两件事以响应的事件:
■ 创建一个方法以响应事件。 该方法必须与委托签名匹配。通常,这意味着它必须返回 void,并接受两个参数: 一个和 EventArgs (或派生的类)。 以下代码演示了这一点:
// C#
private void button1_Click(object sender, EventArgs e)
{
  // Method code
}
■ 添加事件处理程序表明哪种方法应接收事件,因为下面的代码演示:
// C#
this.button1.Click += new System.EventHandler(this.button1_Click);
NOTE .NET 2.0
.NET Framework 2.0 包括一个新的泛型版本,EventHandler 的类型。
当事件发生时, 将运行您指定的方法。
如何引发事件
至少一个你必须要引发事件三件事:
■ 创建委托:
// C#
public delegate void MyEventHandler(object sender, EventArgs e);
■ 创建一个事件成员:
// C#
public event MyEventHandler MyEvent;
■ 调用委托方法内的,在需要时引发该事件按以下代码演示:
// C#
MyEventHandler handler = MyEvent;
EventArgs e = new EventArgs();
if (handler != null)
{
  // Invokes the delegates.
  handler(this, e);
}
此外,您可以从 EventArgs 派生自定义类,如果您要传递到事件处理程序的信息。
属性是什么?

属性描述类型、 方法或属性可以使用称为反射的方法以编程方式查询的方式。属性一些常见用途是:
■ 指定一类要求的安全权限
■ 指定安全权限拒绝降低安全风险
■ Declare 功能 如支持序列化
■ 请通过提供一个标题、 说明和版权通知从系统派生的属性类型的程序集。使用 < > 或[] 属性基类,指定表示法。 下面的代码示例演示如何添加程序集属性:
// C# - AssemblyInfo.cs
[assembly: AssemblyTitle("ch01cs")]
[assembly: AssemblyDescription("Chapter 1 Samples")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft Learning")]
[assembly: AssemblyProduct("ch01cs")]
[assembly: AssemblyCopyright("Copyright © 2006")]
[assembly: AssemblyTrademark("")]
Visual Studio 自动创建一些标准属性为程序集创建一个项目包括标题、 说明、 公司、 指南和版本时。您应编辑这些属性为您创建,因为默认情况下,不包括重要信息如说明每个项目。属性超过描述要其他开发人员的程序集,他们还可以声明要求或功能。要启用一个类来进行序列化示例
您必须添加 Serializable 属性,如以下代码所示:
// C#
[Serializable]
class ShoppingCartItem
{
}
没有 Serializable 属性类是不可序列化。同样,下面的代码使用属性来声明它需要读取 C:/boot.ini 文件。 由于在执行之前,如果没有指定的权限此属性的运行库将引发异常,在执行之前,如果没有指定的权限:
// C#
using System;
using System.Security.Permissions;
[assembly:FileIOPermissionAttribute(SecurityAction.RequestMinimum, Read=@"C:/boot.ini")]
namespace DeclarativeExample
{
 class Class1
 {
 [STAThread]
 static void Main(string[] args)
 {
  Console.WriteLine("Hello, World!");
 }
 }
}
什么是类型转发?
类型转发是使您可以将一种类型从一个程序集 (程序集) 移动到另一个程序集 (程序 B 集),并做到这一种,它不需要重新编译使用的客户端的属性 (在实现 TypeForwardedTo)程序集 A。组件 (程序集) , 所使用的客户端应用程序,您可以使用类型转发移动类型从组件 (程序集) 到另一个程序集船舶更新的组件 (和所需的任何其他程序集) 和客户端应用程序仍能不被重新编译。转发工程只由现有应用程序引用的组件的类型。当您重新生成应用程序时,必须应用程序中使用任何类型的相应的程序集引用。
要将一个类型从一类库移动到另,请按照这些步骤操作:
类型转发是.NET 2.0 中的新功能。
1.将 TypeForwardedTo 属性添加到源类库程序集。
2.剪切源类库中的类型定义。
3.将粘贴到目标类库的类型定义。
4.重新生成这两个库。
下面的代码演示用于将 TypeA 移动到 DestLib 类库属性声明:
// C#
using System.Runtime.CompilerServices;
[assembly:TypeForwardedTo(typeof(DestLib.TypeA))]
实验室: 使用委托创建派生的类Lab: Create a Derived Class with Delegates
--------------------------------49

在本练习您将从您在第 1 课中创建 Person 类派生新类。
1、将Chapter01/Lesson3-Person文件夹从 CD 复制到您的硬盘上并打开 C# 版本或 Visual Basic 版本的 CreateStruct项目。
2、将Person由结构改变成类
3、创建一个新类命名为Manager 它继承自基类Person
// C#
class Manager : Person
{
}
4、增加两个新的公共成员分别是strings: phoneNumber 和 officeLocation。
5、复载该构造函数接受一个phone和办公地址来定义新成员。 您需要将调用基类的构造函数,如下所示下面的代码示例:
// C#
public Manager(string _firstName, string _lastName, int _age,
Genders _gender, string _phoneNumber, string _officeLocation)
: base (_firstName, _lastName, _age, _gender)
{
phoneNumber = _phoneNumber;
officeLocation = _officeLocation;
}
6、复载ToString方法,增加phone和office location 代码如下所示:
// C#
public override string ToString()
{
return base.ToString() + ", " + phoneNumber + ", " + officeLocation;
}
7、修改Main方法,创建一个Manager继承于person对象,然后运行你的程序看看是否达到目的。
实验2: 响应事件
在本次实验,你将创建一个类来响应timer事件。
1、通过使用Visual Studio,创建一个Windows窗体应用项目,项目的名字为TimerEvents。
2、在窗体添加ProgressBar控件。
3、在窗体类,声明一个 System.Windows.Forms.Timer 对象的实例。对象可用于指定毫秒间隔后引发事件。下面的代码生命一个Timer对象。
// C#
System.Windows.Forms.Timer t;
4、在设计器中,查看窗体属性。然后查看事件的列表。双击 Load 事件以自动创建一个事件处理程序在窗体在第一次运行时候。在该方法初始化该Timer对象时候,将间隔设置为一秒,创建一个事件处理程序,Tick 事件并开始计时。如下代码所示:
// C#
private void Timer_Shown(object sender, EventArgs e)
{
t = new System.Windows.Forms.Timer();
t.Interval = 1000;
t.Tick += new EventHandler(t_Tick);
t.Start();
}
5、实现方法,将会对 Timer.Tick事件作出回应。当事件发生时, 向 ProgressBar.Value 属性添加 10。当ProgressBar.Value值为100的时候停止计时。如下代码所示:
// C#
void t_Tick(object sender, EventArgs e)
{
progressBar.Value += 10;
if (progressBar.Value >= 100)
t.Stop();
}
6、运行应用程序以验证它响应每秒计时器事件。
本课概要:
■运用继承从一个基类获得继承。
■使用接口定义一组通用必须由相关类型实现的成员。
■分部类拆分成多个源文件的类定义。
■事件,可以是不同的代码部分中发生时运行指定的方法。
■使用属性来描述程序集、 类型和成员。
■若要将一个类型从一个类库移动到另使用 TypeForwardedTo 属性。

第 4 课: 类型之间转换

你经常会遇到类型转换的问题。举个例子,您可能需要确定Integer是否大于或

 

小于 Double。您可能需要将 Double 传递给需要Integer作为参数的方法。或

 

者,您可能需要一个数字显示为文本。本课介绍如何在两个 Visual Basic 中

 

的类型和 C# 之间转换。 类型转换是 Visual Basic 和 C# 凡显著不同,几个

 

领域之一。

本课程后您将能够:

■类型之间的转换

■应避免装箱

■实现运算符转换

估计课时间: 20 分钟

在 Visual Basic 和 C# 中的转换

默认情况下,Visual Basic 允许而 C# 禁止失去精度的隐式转换的类型之间的

 

隐式转换。

Visual Basic 和 C# 允许隐式转换,如果目标类型可容纳所有可能的值从源类

 

型。 被称为一个扩大转换,它说明了下面的示例:

// C#

int i = 1;

double d = 1.0001;

d = i; // 允许转换.

如果区域或源类型的精度超过目标类型的操作称为一个收缩转换通常需要显式转

 

换。

表 1-7 列出如何执行显式转换

System.Convert:System.IConvertible用来实现类型之间转换。

type.ToString,type.Parse:字符串与基类型 ; 如果引发异常,将不能够转

 

换。

type.TryParse,type.TryParseExact :从基类型转换为字符串 ; 如果转换

 

是不可能返回 false。

注意 .NET2.0

TryParse, TryParseExact, 和 TryCast是.NET2.0新增加内容。如果失败您不

 

得不尝试分析或转换,然后捕获异常。

收缩的转换失败如果源值超出了目标类型的范围或类型之间转换不是定义,因此

 

应将括中的收缩转换 Try 块或使用 TryCast 或 TryParse 并检查返回值。

什么是装箱和拆箱

装箱将值类型转换为引用类型,拆箱是将引用类型转换为值类型。下面的示例通

 

过将整数 (值类型) 转换为对象 (引用类型) 演示装箱:

// C#

int i = 123;

object o = (object) i;

拆箱发生如果您为值类型分配一个引用对象。 下面的示例演示取消装箱:

// C#

object o = 123;

int i = (int) o;

装箱和拆箱的最佳做法

装箱和拆箱导致开销,所以您应该避免他们编程认真重复性任务时。 当您调用

 

一个结构继承自 System.Object 如 ToString 的虚拟方法时也会发生装箱。 

 

请按照这些提示,以避免不必要的装箱操作:

■实现特定于类型的版本 (重载) 接受各种值类型的程序。是比创建几个重载

 

的过程接受一个对象参数的更好做法。

■尽可能使用泛型而不是接受 Object 参数

■定义结构时,重写 ToString、 等于和 GetHash 虚拟成员。

如何在自定义类型中实现转换

您可以为自己定义类型写类型转换的方法。 您选择的方法取决于要执行的转换

 

的类型:

■定义以简化收缩和扩大转换数值类型之间的转换运算符。

■重写 ToString 提供转换为字符串,并覆盖分析提供从字符串转换。

■实现 System.IConvertible,使通过 System.Convert 转换。使用此方法使

 

区域性特定转换。

■实现 TypeConverter 类以便在 Visual Studio 属性窗口中使用的设计时转

 

换。设计时转换超出范围考试和 TypeConverter 类没有涉及在这本书的情况。

设计时转换更多信息。

注意 .NET 2.0

转换运算符是.NET 2.0 中新增的。

定义转换运算符使您可以直接从值类型分配给您自定义的类型。扩大隐关键字用

 

于不能失去精度 ;使用 Narrowing 明确的关键字 的转换可能失去精度的转换

 

例如以下结构定义允许分配至整数值的运算符:

// C#

struct TypeA

{

  public int Value;

  // Allows implicit conversion from an integer.

  public static implicit operator TypeA(int arg)

  {

    TypeA res = new TypeA();

    res.Value = arg;

    return res;

   }

   // Allows explicit conversion to an integer

  public static explicit operator int(TypeA arg)

  {

    return arg.Value;

  }

  // Provides string conversion (avoids boxing).

  public override string ToString()

  {

    return this.Value.ToString();

   }

}

上述类型还重写 ToString 执行字符串转换不装箱。 现在可以将分配整数给该

 

类型直接,如下所示:

// C#

TypeA a; int i;

// Widening conversion is OK implicit.

a = 42; // Rather than a.Value = 42

// Narrowing conversion must be explicit.

i = (int)a; // Rather than i = a.Value

Console.WriteLine("a = {0}, i = {0}", a.ToString(), i.ToString());

若要实现 System.IConvertible 接口,添加 IConvertible 接口,该

类型定义。 然后,使用 Visual Studio 自动实现接口。Visual Studio 插入

 

成员声明为 17 方法包括每个基类型 Boolean.GetTypeCode、 ChangeType 和 

 

IConvertible.ToType 方法。不能实现每个方法和一些 — 如 ToDateTime — 

 

可能会无效。无效方法只是引发异常 — Visual Studio 将自动添加代码以引

 

发异常为您 不能实现任何转换方法。

实现 IConvertible 后自定义的类型可以使用标准的 System.Convert 类,如

 

下所示转换:

// C#

TypeA a; bool b;

a = 42;

// Convert using ToBoolean.

b = Convert.ToBoolean(a);

Console.WriteLine("a = {0}, b = {1}", a.ToString(), b.ToString());

实验室: 安全地执行转换

以下练习显示如何避免隐式转换问题,以便程序可预测函数。 如果您遇到完成

 

工作的问题,已完成的项目可用代码文件夹中附带 CD 上。

行使 1: 检查隐式转换

这次您会研究以确定哪些数字类型允许隐式转换。

1.在 Visual Studio 中创建一个新控制台应用程序。

2.声明的三种值类型实例: Int 16 Int 32,和double。 下面的代码示例演示

 

此:

// C#

Int16 i16 = 1;

Int32 i32 = 1;

double db = 1;

3.尝试将每个变量分配给所有其他,如下面的代码示例所示。

// C#

i16 = i32;

i16 = db;

i32 = i16;

i32 = db;

db = i16;

db = i32;

4.尝试生成您的项目。 编译器是否允许的隐式转换,以及为何?