1、C#知识点 目录 类型 3 枚举 3 装箱和取消装箱 4 数组 4 Is,as操作符 4 结构 4 类 5 继承 5 类成员 5 字段 5 常量 6 只读变量 6 访问修饰符 6 类和结构的可访问性 6 类成员和结构成员的可访问性 6 属性 7 自动实现的属性 7 索引器 7 构造函数 7 实例构造函数 . 8 私有构造函数 . 8 静态构造函数 . 8 构造函数的调用顺序 . 8 方法 8 方法参数 9 值类型参数 . 9 引用类型参数 . 9 this 9 静态成员 10 委托 10 事件 10 多态 11 密封类 12 抽象类 12 接口 13 显式接口实现 13 面向对象设计原则 13
2、 属性 (特性 ) 14 创建自定义属性 (特性 ) 14 泛型 14 泛型类型参数 15 类型参数的约束 15 泛型类 15 泛型接口 16 泛型方法 16 泛型委托 16 泛型代码中的默认关键字 16 可以为 null 的类型 17 异常和异常处理 18 异常处理 18 Catch 块 . 19 Finally 块 19 创建和引发异常 20 引发异常时要避免的情况 20 定义异常类 20 命名空间 21 完全限定名 21 集合类 21 应用程序域 21 线程 22 线程处理 22 线程同步 23 不安全代码 23 类型 类型可描述为: 内置数值类型(如 int 或 char),或 用户定
3、义类型(如 class 或 interface)。 匿名类型,它由一组封装在无名称引用类型中的公共属性组成。 类型还可以定义为: 值类型( C# 参考) (用于存储值)。这些类型包括基元数值类型、枚举和结构,还包括这几种类型 的可以为 null 的版本。 引用类型( C# 参考) (用于存储对实际数据的引用)。这些类型包括类、接口、数组和委托。 引用类型 在内存中不直接存储引用类型的数据,而是存储该数据的地址 。引用类型包括 :类 ,接口 ,数组 ,字符串 枚举 C# 允许您使用 enum 关键字创建自己的命名常量集。 使用这些数据类型可以声明一组名称或其他文本值,用于定义可以赋给某个变量的所
4、有可能值。 本质上是整数 默认第一个枚举项 :=0 以后的项自动 +1 可以给单独的项定义特定的值 装箱和取消装箱 装箱和取消装箱使值类型能够被视为对象。对值类型装箱将把该值类型打包到 Object 引用类型的一个实例中。这使得值类型可以存储于垃圾回收堆中。取消装箱将从对象中提取值类型。 数组 数组的概念 数组是一种数据结构,它包含若干相同类型的变量。 数组是相同类型的对象的集合。由于数组几乎可以为任意长度,因此可以使用数组存储数千乃至数 百万个对象,但必须在创建数组时就确定其大小。数组中的每项都按索引进行访问,索引是一个数字,指示对象在数组中的存储位置或槽。数组既可用于存储引用类型,也可用于
5、存储值类型。 声明和创建数组 通过索引来访问数组 遍历数组中的所有元素 使用 for和 foreach 循环语句来遍历数组中的元素 Is,as操作符 在确定对象实现了某个特定接口之后,就可以引用该接口。为了引用某接口,可以把对象类型强制转换为接口类型 。 is 和 as 操作符也适用于其他类型。在运行时,可以用它们来确定类的类型 结构 C# 中的结构与 类相似,但结构缺乏某些功能,例如继承。另外,由于结构是一个值类型,因此通常创建结构要比创建类的速度快。如果您有一些紧凑循环,需要在其中创建大量新数据结构,则应考虑使用结构而不是类。 把一系列相关的变量组织成单一实体的过程,在 C#中称为生成结构
6、的过程。这个单一实体的类型就叫做结构类型,每一个变量称为结构的成员 类 类可以从其他类中继承。这是通过以下方式实现的:在声明类时,在类名称后放置一个冒号,然后在冒号后指定要从中继承的类(即基类)。 类具有以下特点: 与 C+ 不同, C# 只支持单继承:类只能从一个基类 继承实现。 一个类可以实现多个接口。 类定义可在不同的源文件之间进行拆分。 静态类是仅包含静态方法的密封类。 继承 继承是指派生类可以获得其基类(此基类本身也可以是派生类)特征的能力。继承允许把行为的公共集合(定义为属性和方法)包含在基类中,这些公共集合可以在基类的派生类中得到重用。利用继承性,可以以现有的通用类型为基础,创建
7、出新的特殊类型。 .NET中所有的类 ,都直接或间接继承自 System.Object类 类 成员 字段 字段是类或结构中的对象或值。类和结构使用字段可以封装数据。 字段可标记为 public、 private、 protected、internal 或 protected internal。 可以选择将字段声明为 static。这使得调用方在任何时候都能使用字段,即使类没有任何实例。有关更多信息,请参见 静态类和静态类成员( C# 编程指南) 。 可以将字段声明为 readonly。只 读字段只能在初始化期间或在构造函数中赋值。 static readonly 字段非常类似于常数,只不过 C
8、# 编译器不能在编译时访问静态只读字段的值,而只能在运行时访问。 常量 常数是在编译时已知并保持不变的值。常数被声明为字段,方法是在字段的类型前面使用 const 关键字。 只读变量 readonly 关键字是可以在字段上使用的修饰符。当字段声明包括 readonly 修饰符时,该声明引入的字段赋值只能作为声明的一部分出现,或者出现在同一类的构造函数中。 访问修饰符 可以限制类和结构,以便只有声明它们的 程序或命名空间才能使用它们。可以限制类成员,以便只有派生类才能使用它们,或者限制类成员,以便只有当前命名空间或程序中的类才能使用它们。访问修饰符是添加到类、结构或成员声明的关键字,用以指定这些
9、限制。这些关键字包括 public、 private、 protected 和 internal。 public: 对任何类和成员都公开 , 无限制访问 ; protected: 仅仅对该类以及该类的派生类公开 ; private: 仅仅对该类公开 ; internal: 只能值包含该类的程序集中访问该类 ; protected internal: 只能在本类 ,派生类或者包含该类的程序集中访问 类和结构的可访问性 没有嵌套在其他类或结构中的类和结构可以是公共的,也可以是内部的。声明为公共的类型可由任何其他类型访问。声明为内部的类型只能由同一程序集中的类型访问。默认情况下,类和结构声明为内部的
10、,除非向类定义添加了关键字 public,如前面的示例所示。类或结构定义可以添加 internal 关键字,使其访问级别成为显式的。访问修饰符不影响类或结构自身;它始终能够访问自身及其所有成员。 类成员和结构成员的可访问性 可以使用五种访问类型之 一来声明类成员或结构成员。就像类和结构自身一样,它们也可以是公共的或内部的。如果使用 protected 关键字将类成员声明为受保护的,则只有使用该类作为基类的派生类型才能访问该成员。通过组合 protected 和 internal 关键字,可以将类成员标记为 protected internal;只有派生类型或同一程序集中的类型才能访问该成员。最
11、后,可以使用 private 关键字将类成员或结构成员声明为私有的,指示只有声明该成员的类或结构才能访问该成员。 protected internal 可访问性 的意思是受保护 “或 ”内部,而不是受保护 “和 ”内部。换句话说,可以从同一程序集内的任何类(包括派生类)中访问 protected internal 成员。若要限制为只有同一程序集内的派生类可以访问,请将类本身声明为内部,并将其成员声明为受保护。 属性 属性是这样的成员:它们提供灵活的机制来读取、编写或计算私有字段的值。可以像使用公共数据成员一样使用属性,但实际上它们是称作 “访问器 ”的特殊方法。属性使类能够以一种公开的方法获取
12、和设置值,同时隐藏实现或验证代码。 get 属性访问器用于返回属性值,而 set 访问 器用于分配新值。这些访问器可以有不同的访问级别。 value 关键字用于定义由 set 索引器分配的值。 不实现 set 方法的属性是只读的。 对于不需要任何自定义访问器代码的简单属性,可考虑选择使用自动实现的属性。 自动实现的属性 当属性访问器中不需要其他逻辑时,自动实现的属性可使属性声明变得更加简洁。当您如下面的示例所示声明属性时,编译器将创建一个私有的匿名后备字段,该字段只能通过属性的 get 和 set 访问器进行访问。 索引器 索引器允许类或结构的实例就像数组一样进行索引。索引器类似于属性,不同之
13、 处在于它们的访问器采用参数。 使用索引器可以用类似于数组的方式为对象建立索引。 get 访问器返回值。 set 访问器分配值。 this 关键字用于定义索引器。 value 关键字用于定义由 set 索引器分配的值。 索引器不必根据整数值进行索引,由您决定如何定义特定的查找机制。 索引器可被重载。 索引器可以有多个形参,例如当访问二维数组时。 构造函数 构造函数的名字必须与类同名 构造函数没有返回类型 ,它可以带参数 ,也可以不带参数 声明类对象时 ,系统自动调用构造函数,构造函数不能被显式调用 构造函数可以重载 ,从而提供初始化类对象的不同方法 若在声明时未定义构造函数 ,系统会自动生成默
14、认的构造函数 ,此时构造函数的函数体为空 实例构造函数 实例构造函数用于创建和初始化实例。创建新对象时将调用类构造函数 私有构造函数 私有构造函数是一种特殊的实例构造函数。它通常用在只包含静态成员的类中。如果类具有一个或多个私有构造函数而没有公共构造函数,则其他类(除嵌套类外)无法创建该类的实例。 静态构造函数 静态构造函数用于初始化任何静态数据,或用于执行仅需执行一次的特定操作。在创建第一个实例或引用任何静态成员之前,将自动调用静 态构造函数。 静态构造函数具有以下特点: 静态构造函数既没有访问修饰符,也没有参数。 在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数来初始化类。
15、无法直接调用静态构造函数。 在程序中,用户无法控制何时执行静态构造函数。 静态构造函数的典型用途是:当类使用日志文件时,将使用这种构造函数向日志文件中写入项。 静态构造函数在为非托管代码创建包装类时也很有用,此时该构造函数可以调用 LoadLibrary 方法。 构造函数的调用顺序 因为派生类要使用基类,所以基类实例化必须在派生类实例化之前进行 如果想要调用基类的非默认构造函数,那么必须使用 base 关键字。 方法 “方法 ”是包含一系列语句的代码块。在 C# 中,每个执行指令都是在方法的上下文中执行的。 方法是通过指定访问级别、返回值、方法名称和任何方法参数在类或结构中声明的。这些部分统称
16、为方法的“签名 ”。 方法参数括在括号中,并用逗号隔开。空括号表示方法不需要参数。 方法参数 值类型参数 值类型变量直接包含其数据,这与引用类型变量不同,后者包含对其数据的引用。因此,向方法传递值类型变量意味着向方法传递变量的一个副本。方法内发生的对参数的更改对该 变量中存储的原始数据无任何影响。 引用类型参数 引用类型的变量不直接包含其数据;它包含的是对其数据的引用。当通过值传递引用类型的参数时,有可能更改引用所指向的数据,如某类成员的值。但是无法更改引用本身的值;也就是说,不能使用相同的引用为新类分配内存并使之在块外保持。若要这样做,应使用 ref 或 out 关键字传递参数。 ref 和
17、 out参数 ref关键字 :按引用传值 Reference:引用 :强制要求参数按引用 传值 注意 : 1.传入参数之前 ,必须给参数赋值 2.调用方法时 ,必须加 ref 关键字 out关键字 :输出参数 注意 : 1.传参数到方法之前 ,可以不先赋值 2.在方法内部 ,必须要有给参数赋值 语句 this 代表当前这个实例 使用只有 2个领域 在类内部访问自己类的成员 静态成员 静态的 ,不需要创建类的实例 ,就可以直接通过类名来访问 委托 委托是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的调用可以像其他任何方法一样,具有参数和返回值 。 委托具有
18、以下特点: 委托类似于 C+ 函数指针,但它们是类型安全的。 委托允许将方法作为参数进行传递。 委托可用于定义回 调方法。 委托可以链接在一起;例如,可以对一个事件调用多个方法。 方法不必与委托签名完全匹配。 C# 2.0 版引入了匿名方法的概念,此类方法允许将代码块作为参数传递,以代替单独定义的方法。 C# 3.0 引入了 Lambda 表达式,利用它们可以更简练地编写内联代码块。匿名方法和 Lambda 表达式(在某些上下文中)都可编译为委托类型。这些功能统称为匿名函数。 事件 类或对象可以通过事件向其他类或对象通知发生的相关事情。发送(或引发)事件的类称为 “发行者 ”,接收(或处理)事
19、件的类称为 “订户 ”。 事件具有 以下特点: 发行者确定何时引发事件,订户确定执行何种操作来响应该事件。 一个事件可以有多个订户。一个订户可处理来自多个发行者的多个事件。 没有订户的事件永远不会被调用。 事件通常用于通知用户操作,例如,图形用户界面中的按钮单击或菜单选择操作。 如果一个事件有多个订户,当引发该事件时,会同步调用多个事件处理程序。要异步调用事件,请参见使用异步方式调用同步方法。 可以利用事件同步线程。 在 .NET Framework 类库中,事件是基于 EventHandler 委托和 EventArgs 基类的。 事件的工 作方式 使用 Event 语句创建事件 Event
20、Handler 委托的实现方法 自定义事件参数类 事件和委托的关系 多态 通过继承,一个类可以用作多种类型:可以用作它自己的类型、任何基类型,或者在实现接口时用作任何接口类型。这称为多态性。 C# 中的每种类型都是多态的。类型可用作它们自己的类型或用作 Object 实例,因为任何类型都自动将 Object 当作基类型。 多态性是一个面向对象的概念,它允许以相似的方式来对待所有派生类,尽管这些派生类是各不相同的。创建派生类的目的是为了获得更多的特殊功能 。 允许同一种功能 ,有多种实现 当派生类从基类继承时,它会获得基类的所有方法、字段、属性和事件。若要更改基类的数据和行为,您有两种选择:可以
21、使用新的派生成员替换基成员,或者可以重写虚拟的基成员。 使用新的派生成员替换基类的成员需要使用 new 关键字。如果基类定义了一个方法、字段或属性,则 new 关键字用于在派生类中创建该方法、字段或属性的新定义。 new 关键字放置在要替换的类成员的返回类型之前。 virtual 关键字用于修饰方法、属性、索引器或事件声明,并使它们可以在派生类中被重写。 调用虚方法时,将 为重写成员检查该对象的运行时类型。将调用大部分派生类中的该重写成员,如果没有派生类重写该成员,则它可能是原始成员。 默认情况下,方法是非虚拟的。不能重写非虚方法。 virtual 修饰符不能与 static、 abstrac
22、t、 private 或 override 修饰符一起使用。 除了声明和调用语法不同外,虚拟属性的行为与抽象方法一样。 在静态属性上使用 virtual 修饰符是错误的。 通过包括使用 override 修饰符的属性声明,可在派生类中重写虚拟继承属性。 要扩展或修改继承的方法、属性、索引器 或事件的抽象实现或虚实现,必须使用 override 修饰符。 override 方法提供从基类继承的成员的新实现。由 override 声明重写的方法称为重写基方法。重写的基方法必须与 override 方法具有相同的签名。有关继承的信息,请参见 继承( C# 编程指南) 。 不能重写非虚方 法或静态方法
23、。重写的基方法必须是 virtual、 abstract 或 override 的。 override 声明不能更改 virtual 方法的可访问性。 override 方法和 virtual 方法必须具有相同的访问级别修饰符。 不能使用 new、 static、 virtual 或 abstract 修饰符来修改 override 方法。 重写属性声明必须指定与继承属性完全相同的访问修饰符、类型和名称,并且被重写的属性必须是 virtual、abstract 或 override 的。 密封 类 sealed 当对一个类应用 sealed 修饰符时,此修饰符会阻止其他类从该类继承。 密封的
24、,”封印” :不允许被继承或扩展 (阻止继承和扩展 类 :不允许再被继承 方法 :不允许再被重写 sealed override 子类提供一种父类的成员的新的实现 ,但是 ,禁止后续子类再提供新的实现 当应用于方法或属性时, sealed 修饰符必须始终与 override 一起使用。 抽象类 abstract 修饰符可以和类、方法、属性、索引器及事件一起使用。在类声明中使用 abstract 修饰符以指示某个类只能是其他类的基类。标记 为抽象或包含在抽象类中的成员必须通过从抽象类派生的类来实现。 抽象类具有以下特性: 抽象类不能实例化。 抽象类可以包含抽象方法和抽象访问器。 不能 seale
25、d修饰符修改抽象类,这意味着抽象类不能被继承。 从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实实现。 在方法或属性声明中使用 abstract 修饰符以指示方法或属性不包含实现。 抽象方法具有以下特性: 抽象方法是隐式的虚方法。 只允许在抽象类中使用抽象方法声明。 因为抽象方法声明不提供实际的 实现,所以没有方法体;方法声明只是以一个分号结束,并且在签名后没有大括号 ( )。 实现由一个重写方法 override提供,此重写方法是非抽象类的一个成员。 在抽象方法声明中使用 static 或 virtual 修饰符是错误的。 除了在声明和调用语法上不同外,抽象属性的行为与抽象方
26、 法一样。 在静态属性上使用 abstract 修饰符是错误的。 在派生类中,通过包括使用 override 修饰符的属性声明,可以重写抽象的继承属性。 抽象类必须为所有接口成员提供实现。 实现接口的抽象类可以将接口方法映射到抽象方法上。 接口 接口是引用类型,是一系列需要实现的功能的定义 . 接口是使用 interface 关键字定义的。接口描述的是可属于任何类或结构的一组相关功能。接口可由方法、属性、事件、索引器或这四种成员类型的任意组合构成。接口不能包含字段。接口成员一定是公共的。 类和结构可以按照类继承 基类或结构的类似方式继承接口,但有两个例外: 类或结构可继承多个接口。 类或结构继
27、承接口时,仅继承方法名称和签名,因为接口本身不包含实现。 若要实现接口成员,类中的对应成员必须是公共的、非静态的,并且与接口成员具有相同的名称和签名。类的属性和索引器可以为接口上定义的属性或索引器定义额外的访问器。例如,接口可以声明一个带有 get 访问器的属性,而实现该接口的类可以声明同时带有 get 和 set 访问器的同一属性。但是,如果属性或索引器使用显式实现,则访问器必须匹配。 接口和接口成员是抽象的;接口不提供默 认实现。 接口具有下列属性: 接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员。 不能直接实例化接口。 接口可以包含事件、索引器、方法和属性。 接口不
28、包含方法的实现。 类和结构可从多个接口继承。 接口自身可从多个接口继承。 显式接口实现 如果类实现两个接口,并且这两个接口包含具有相同签名的成员,那么在类中实现该成员将导致两个接口都使用该成员作为它们的实现。然而,如果两个接口成员执行不同的函数,那么这可能会导致其中一个接口的实现不正确或两个接口的实现都不正确。可以显式地实现接口成员 - 即创建一个仅通过该接口调用并且特定于该接口的类成员。这是使用接口名称和一个句点命名该类成员来实现的。 面向对象设计原则 开放封闭原则 高内聚、低耦合原则 优先使用对象组合,而不是类继承原则 针对接口编程,而不是针对实现编程 封装变化点 属性 (特性 ) 属性提
29、供功能强大的方法以将声明信息与 C# 代码(类型、方法、属性等)相关联。属性与程序实体关联后,即可在运行时使用名为 “反射 ”的技术查询属性。 属性以两种形式出现: 一种是在公共语言运行库 (CLR) 中定义的属性。 另一种是可以创建的用于向代码中添加附加信息 的自定义属性。此信息可在以后以编程方式检索。 属性具有以下特点: 属性可向程序中添加元数据。元数据是嵌入程序中的信息,如编译器指令或数据描述。 程序可以使用反射检查自己的元数据。 通常使用属性与 COM 交互。 创建自定义属性 (特性 ) 通过定义一个属性类,可以创建您自己的自定义属性。该属性类直接或间接地从 Attribute 派生,
30、有助于方便快捷地在元数据中标识属性定义。假设您要用编写类或结构的程序员的名字标记类和结构。 泛型 泛型类和泛型方法同时具备可重用性、类型安全和效率,这是非泛型类和非泛型方法无法具 备的。泛型通常用与集合以及作用于集合的方法一起使用。 .NET Framework 2.0 版类库提供一个新的命名空间 System.Collections.Generic,其中包含几个新的基于泛型的集合类。 使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。 泛型最常见的用途是创建集合类。 .NET Framework 类库在 System.Collections.Generic 命名空间中包含几个新
31、的泛型集合类。应尽可能地使用这些类来代替普通的类,如 System.Collections 命 名空间中的 ArrayList。 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。 可以对泛型类进行约束以访问特定数据类型的方法。 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。 泛型类型参数 在泛型类型或方法定义中,类型参数是客户端在实例化泛型类型的变量时指定的特定类型的占位符。泛型类中列出的 GenericList)不可以像这样使用,因为它实际上并不是一个类型,而更像是一个类型的蓝图。若要使用 GenericList,客户端代码必须通过指定尖括号中的类型参数来 声
32、明和实例化构造类型。此特定类的类型参数可以是编译器识别的任何类型。 类型参数的约束 在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。这些限制称为约束。约束是使用 where 上下文关键字指定的。下表列出了六种类型的约束: 约束 说明 T:结构 类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息,请参见 使用可以为 null 的类型( C# 编程指南) 。 T:类 类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。 T: new() 类型参
33、数必须具有无参数的公共构造函数。当与其他约束一起使用时, new() 约束必须最后指定。 T: 类型参数必须是指定的基类或派生自指定的基类。 T: 类型参数必须是指定的接口或实 现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。 T: U 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。 泛型类 一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。创建您自己的泛型类时,需要特别注意以下事项: 将哪些类型通用化为类型参数。 通常,能够参数化的类型越多,代码就会变得越灵活,重用
34、性就越好。但是,太多的通用化会使其他开发人员难以阅读或理解代码。 如果存在约束,应对类型 参数应用什么约束。 一条有用的规则是,应用尽可能最多的约束,但仍使您能够处理必须处理的类型。例如,如果您知道您的泛型类仅用于引用类型,则应用类约束。这可以防止您的类被意外地用于值类型,并允许您对 T 使用 as 运算符以及检查空值。 是否将泛型行为分解为基类和子类。 由于泛型类可以作为基类使用,此处适用的设计注意事项与非泛型类相同。请参见本主题后面有关从泛型基类继承的规则。 是否实现一个或多个泛型接口。 例如,如果您设计一个类,该类将用于创建基于泛型的集合中的项,则可能必须实现一个接口,如 ICompar
35、able),其中 T 是您的类的类型。 泛型接口 为泛型集合类或表示集合中项的泛型类定义接口通常很有用。对于泛型类,使用泛型接口十分可取,例如使用 IComparable) 而不使用 IComparable,这样可以避免值类型的装箱和取消装箱操作。 .NET Framework 类库定义了若干泛型接口,以用于 System.Collections.Generic 命名空间中的集合类。 泛型方法 泛型方法是使用类型参数声明的方法 。 相同的类型推理规则也适用于静态方法和实例 方法。编译器能够根据传入的方法实参推断类型形参;它无法仅从约束或返回值推断类型形参。因此,类型推理不适用于没有参数的方法。
36、类型推理在编译时、编译器尝试解析重载方法签名之前进行。编译器向共享相同名称的所有泛型方法应用类型推理逻辑。在重载解析步骤中,编译器仅包括类型推理取得成功的那些泛型方法。 泛型方法可以使用许多类型参数进行重载。 泛型委托 引用泛型委托的代码可以指定类型参数以创建已关闭的构造类型,就像实例化泛型类或调用泛型方法一样 。在泛型类内部定义的委托使用泛型类类型参数的方式可以与类方法所使用的方式相同。 根据典型设计模式定义事件时,泛型委托尤其有用,因为发送方参数可以为强类型,不再需要强制转换成 Object,或反向强制转换。 泛型代码中的默认关键字 在泛型类和泛型方法中产生的一个问题是,在预先未知以下情况
37、时,如何将默认值分配给参数化类型 T: T 是引用类型还是值类型。 如果 T 为值类型,则它是数值还是结构。 给定参数化类型 T 的一个变量 t,只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。解决方案是使用 default 关键字,此关键字对于引用类型会返回 null,对于数值类型会返回零。对于结构,此关键字将返回初始化为零或 null 的每个结构成员,具体取决于这些结构是值类型还是引用类型。以下来自 GenericList 类的示例显示了如何使用 default 关键字。 可以为 null 的类型 可空类型表示可
38、被赋值为 null 值的值类型变量。无法创建基于引用类型的可空类型。(引用类型已支持 null 值。)。 语法 T? 是 System.Nullable 的简写,此处的 T 为值类型。这两种形式可以互换。 为可空类型赋值与为一般值类型赋值的方法相同,如 int? x = 10; 或 double? d = 4.108;。 如果基础类型的值为 null,请使用 System.Nullable.GetValueOrDefault 属性返回该基础类型所赋的值或默认值,例如 int j = x.GetValueOrDefault(); 请使用 HasValue 和 Value 只读属性测试是否为空和检
39、索值,例如 if(x.HasValue) j = x.Value; 如果此变量包含值,则 HasValue 属性返回 True;或者,如果此变量的值为空,则返回 False。 如果已赋值,则 Value 属性返回该值,否则将引发 System.InvalidOperationException。 可空类型变量的默认值将 HasValue 设置为 false。未定义 Value。 使用 ? 运算符分配默认值,当前值为空的可空类型被赋值给非空类型时将应用该默认值,如 int? x = null; int y = x ? -1;。 不允许使用嵌套的可空类型。将不编译下面一行: Nullable n;
40、 可空类型可以表示基础类型的所有值,另外还可以表示 null 值。可空类型可通过下面两种方式中的一种声明: System.Nullable variable - 或 - T? variable T 是可空类型的基础类型。 T 可以是包括 struct 在内的任何值类型;但不能是引用类型。 空类型的每个实例都具有两个公共的只读属性: HasValue HasValue 属于 bool 类型。当变量包含非空值时,它被设置为 true。 Value Value 的类型与基础类型相同。如果 HasValue 为 true,则说明 Value 包含有意义的值。如果 HasValue 为 false,则访
41、问 Value 将引发 InvalidOperationException。 异常和异常处理 异常处理 C# 语言的异常处理功能可帮助您处理程序运行 时出现的任何意外或异常情况。异常处理使用 try、 catch 和 finally 关键字来尝试可能未成功的操作、处理失败,以及在事后清理资源。异常可以由公共语言运行库 (CLR)、第三方库或使用 throw 关键字的应用程序代码生成。 异常具有以下特点: 在应用程序遇到异常情况(如被零除情况或内存不足警告)时,就会产生异常。 在可能引发异常的语句周围使用 try 块。 try 块中发生异常后,控制流会立即跳转到关联的异常处理程序(如果存在)。
42、如果给定异常没有异常处理程序,则程序将停止执行,并显示一条错误消 息。 如果 catch 块定义了一个异常变量,则可以使用它来获取有关所发生异常的类型的更多信息。 可能导致异常的操作通过 try 关键字来执行。 异常处理程序是在异常发生时执行的代码块。在 C# 中, catch 关键字用于定义异常处理程序。 程序可以使用 throw 关键字显式地引发异常。 异常对象包含有关错误的详细信息,比如调用堆栈的状态以及有关错误的文本说明。 即使引发了异常, finally 块中的代码也会执行,从而使程序可以释放资源。 在 C# 中,程序中的运行时错误通过使用一种称为 “异常 ”的机制在程 序中传播。异
43、常由遇到错误的代码引发,由能够更正错误的代码捕捉。异常可由 .NET Framework 公共语言运行库 (CLR) 或由程序中的代码引发。一旦引发了一个异常,这个异常就会在调用堆栈中往上传播,直到找到针对它的 catch 语句。未捕获的异常由系统提供的通用异常处理程序处理,该处理程序会显示一个对话框。 异常由从 Exception 派生的类表示。此类标识异常的类型,并包含详细描述异常的属性。引发异常涉及到创建一个异常派生类的实例,配置异常的属性(可选),然后使用 throw 关键字引发该对象。 在引发异常之后,运行库检查当前语句以确定它是否在 try 块中。如果是,则检查与该 try 块关联
44、的任何 catch 块,以确定它们是否能够捕获该异常。 Catch 块通常会指定异常类型;如果该 catch 块的类型与异常或异常的基类的类型相同,则该 catch 块就能够处理该方法。 如果引发异常的语句不在 try 块中,或者包含该语句的 try 块没有匹配的 catch 块,运行库将检查调用方法中是否有 try 语句和 catch 块。运行库将在调用堆栈中向上继续搜索兼容的 catch 块。在找到并执行 catch 块之后,控制权将传递给 catch 块之后的第一个语句。 一个 try 语句可能包含多个 catch 块。将执行第一个能够处理该异常的 catch 语句;任何后续的 catc
45、h 语句都将被忽略,即使它们是兼容的也如此。 在执行 catch 块之前,将检查由运行库评估过的 try 块和包含兼容的 catch 块的 try 块中是否有 finally 块。 Finally 块可让程序员清理中止的 try 块可能留下的任何不明确状态,或释放任何外部资源(如图形句柄、数据库连接或文件流),而不用等待运行库 中的垃圾回收器来终结这些对象。 如果在引发异常之后没有在调用堆栈上找到兼容的 catch 块,则会出现三种情况中的一种: 如果异常出现在析构函数中,则中止该析构函数并调用基析构函数(如果有)。 如果调用堆栈包含静态构造函数或静态字段初始值设定项,则引发一个 TypeIn
46、itializationException,并将原始异常分配给新异常的 InnerException 属性。 如果到达线程的开头,则终止线程。 C# 程序员使用 try 块来对可能受异常影响的代码进行分区,并使用 catch 块来处理所产生 的任何异常。可以使用 finally 块来执行代码,而无论是否引发了异常。有时需要这样做,因为如果引发了异常,将不会执行 try/catch 构造后面的代码。 try 块必须与 catch 或 finally 块一起使用,并且可以包括多个 catch 块。不带有 catch 或 finally 块的 try 语句将导致编译器错误。 Catch 块 catc
47、h 块可以指定要捕捉的异常类型。此类型称为 “异常筛选器 ”,它必须是 Exception 类型或者从此类型派生。应用程序定义的异常应当从 ApplicationException 派生。 具有不同异常筛选器的多个 catch 块可以串联在一起。多个 catch 块的计算顺序是从顶部到底部,但是,对于所引发的每个异常,都只执行一个 catch 块。与所引发异常的准确类型或其基类最为匹配的第一个 catch 块将被执行。如果没有任何 catch 块指定匹配的异常筛选器,则将执行不带筛选器的 catch 块(如果有的话)。需要将带有最具体的(即派生程度最高的)异常类的 catch 块放在最前面。
48、当下列条件为真时,应该捕捉异常: 对引发异常的原因有具体的了解,并可实现 特定的恢复,例如,捕捉 FileNotFoundException 对象并提示用户输入新的文件名。 可以新建一个更具体的异常并引发该异常。 部分处理异常。例如,可以使用 catch 块向错误日志中添加项,但随后重新引发该异常,以便对该异常进行后续处理。 Finally 块 可以使用 finally 块清理在 try 块中执行的操作。如果存在 finally 块,它将在执行完 try 和 catch 块之后执行。finally 块始终会执行,而与是否引发异常或者是否找到与异常类型匹配的 catch 块无关。 可以使用 finally 块释放资源(如文件流、数据库连接和图形句柄),而不用等待由运行库中的垃圾回收器来完成对象。在此示例中, finally 块用来关闭在 try 块中打开的文件。注意,在关闭文件句柄之前要检查它的状态。如果 try 块没有打开该文件,则文件句柄仍然设置为 null。或者,如果成功打开文件并且没有引发异常,则 finally 块仍将执行,并且将关闭打开的文件。 创建和引发异常 异常用于指示在运行程序时发生了错误。此时将创建一个描述错误的异常对象,然后使用 throw 关键字 “引发 ”该对