跳转到内容

.NET 开发基金会/AllInOne

来自维基教科书,开放世界中的开放书籍


Microsoft .NET Framework 2.0
应用程序开发基础

欢迎来到维基教科书的 Microsoft 认证考试 70-536 学习指南的“多合一”视图页面。考虑到它的规模,此页面没有配置为编辑(请使用 主页)。


前言

欢迎使用“Microsoft .NET Framework 2.0 应用程序开发基础”模块。

本书对与 Microsoft 认证计划 的考试 70-536 相关的考试目标进行了广泛的“教科书式”覆盖。这些目标涵盖了基本 .NET 类库以及程序与 .NET 执行环境的交互。

请告诉我们您对该模块的反馈,以便我们对其进行改进(请参见 讨论页面)。

前言包含与文档上下文、作者、维基教科书等相关的信息。如果您主要对模块内容感兴趣,可以跳过到 简介

受众和其他维基教科书资源

本学习指南的受众是专业从事 .NET 框架的软件开发人员。Microsoft 在考试(70-536)准备指南 中指出:“考生应至少拥有两年到三年使用 .NET Framework 1.0、.NET Framework 1.1 和 .NET Framework 2.0 开发基于 Web 的、基于 Microsoft Windows 的或分布式应用程序的经验。考生应熟练掌握 Visual Studio 2005。”。

对于本学习指南,我们只假设读者至少了解一种 .NET 语言(VB.NET、C# 等),可以使用 Visual Studio,并且具有一定的使用经验。

请注意,.NET 平台非常广泛,并且还假设您对计算机科学有基本了解。例如,我们将简要讨论面向对象原则如何在框架中实现(从开发人员的角度),但不会讨论原则本身,它们来自哪里,它们解决了哪些具体问题等等。

维基教科书有其他关于 VB、C# 和 .NET 的书籍,这些书籍更具入门性质。

最后,我们所说的“专业感兴趣”指的是,本书和其他认证学习指南对框架进行了广泛的介绍,并且需要大量的学习时间。所以任何想要获得这种介绍并且有时间的人都可以加入进来!

贡献

此模块远未完成!请随时根据需要改进它。

如果您是维基教科书的新贡献者,这里有一些提示

  • 维基教科书是关于教科书,而不是“标准”维基。项目团队正在寻找可以从头到尾阅读的连续叙事。对于习惯了 MSDN 类型文档的人来说,这可能是一个很大的调整。
  • 只包含链接的页面不符合上述图片,因此应积极避免。如果您创建了一个页面,请确保在上面添加内容 :-)
  • 管理员使用模板来询问问题或通知未完成的任务。这些模板会产生相当醒目的通知,乍一看可能会让人惊讶。不要担心外观,并提出澄清问题。管理员非常乐于助人,并且会尽力帮助您解决任何问题。
  • 代码示例非常受欢迎,应放在“隐藏”部分中,以保留文本的流程。请参见 示例部分。示例应尽可能简短,但印刷材料的空间限制显然在这里不适用。因此,我们鼓励您使用完整的、简洁明了的程序,以便可以对其直接测试和使用。

考试信息和认证

有关考试的最新信息可以在 MSDN 考试信息 中找到。

此模块是获得许多 Microsoft 认证的第一个考试(70-536)的学习指南。

作者

William “Scott” Baker (用户:Scott98390)

如果您对本书进行贡献,并且愿意的话,请在这里添加您的姓名。特定文章的贡献者可以通过其历史记录追溯。

请注意,维基教科书的政策不是创建只包含链接的页面或文本非常少的页面。首选的方式是拥有可以从头到尾阅读的连续教科书。对该模块的最初贡献是为每个三级或四级考试目标创建单独的页面。这导致了大量页面被合并到更一致的全局页面中。对于只包含链接的页面,这个过程产生了负面影响,即失去了对这些页面的贡献的引用。对于包含文本的页面,更改历史记录已迁移到合并的页面中。


简介

模块目标

本学习指南旨在替代其他用于准备通过 70-536 考试的资源。

  • 我们提供了指向 MSDN 库的链接,但并没有试图取代它。通过考试的人经常指出,培训工具包 没有涵盖考试的各个方面,并且查阅 MSDN 文档是一个很大的优势。
  • 我们提供了指向维基百科和其他维基媒体项目的链接,这些链接在适用时会使用。这不是一系列理论或百科全书文章,而是关于如何在 .NET 框架中实现这些概念以及如何使用它们的指南。
  • 我们不提供测试软件
  • 我们不假装此模块可以替代 Microsoft 推荐的培训工具包或任何其他推荐材料。

但是,即使是在此文档极其不完整的状态下,我们也提供了以下内容:

  • Microsoft 列出的考试目标的详尽列表
  • 从所有考试目标到相应 MSDN 库文章的链接。
  • 提供越来越多的主题的“教科书式”解释,并链接到相关的外部资源。
  • 最重要的是,它提供了一个地方,让你可以在考试前存放重要的笔记和代码示例,以便复习,并在考试后作为专业工作参考。没有哪个培训工具包或库能在共享和受控的环境中提供这种功能。

像这样一本书最难的部分是保持平衡,既要有足够的篇幅来涵盖和解释考试的每个目标,又不能太多,因为阅读和学习时间应该保持在可控的水平。此外,没有必要在这里涵盖与考试目标无关的内容。

最后,作为维基媒体家族(维基百科等)的一部分,维基教科书项目在版权和总体质量方面拥有非常高的道德标准。如果你发现任何地方有任何“错误”,请不要犹豫,进行更正。

模块结构

本模块围绕微软为 70-536 考试设定的目标而构建。第 3 章到第 9 章代表考试的 7 个主要目标类别。

坚持考试“官方”目标的理念是,我们假设认证提供者(微软)对产品(.NET)有一定的了解,并且可以说明哪些是需要了解的重要内容。

对于每一章

  • 第一部分涵盖本章中讨论的主要概念(主题)。这个“主题”部分允许我们将“是什么”与“怎么做”分开。请注意,考试侧重于库和工具的使用,仅仅了解概念是不足以应付考试的。
  • 第二部分详细介绍该目标类别中每个二级、三级和四级考试目标(怎么做、详细用法和 MSDN 库的参考)。

最终,我们希望拥有

  • 每一章的“复习问题”部分,在那里我们可以讨论考试中可能会问到的问题类型。
  • 一个“高级”部分,在那里我们可以放置更高级的材料,并将基本部分的文本流保持在与考试要求的知识水平相符的水平。

截至 2007 年 12 月 7 日

  • 24 个主题在单独的页面上详细介绍(主题标题是链接)。大多数主题最终将被整合到主页面,以实现更好的文本流。
  • 所有主题都直接链接到微软软件开发者网络 (MSDN) 库,大约 480 个主题直接从主页面链接(标题后面的“MSDN”是链接),其他主题则从各自的子页面链接。
  • 我们刚刚开始将“主题”部分链接到维基百科文章,让你了解这些概念是如何在微软世界之外定义和处理的。

有些部分比较高级,另一些部分正在等待你的贡献。更高级的部分并不都在模块的开头。

.NET Framework

在包含考试 70-536“Microsoft .NET Framework 2.0 应用程序开发基础”的认证路径中,它代表了认证流程的第一步。因此,从整体上简要讨论框架,作为这份第一个学习指南的开始是自然而然的。

MSDN .NET 主页 上的定义是:“.NET Framework 是微软的托管代码编程模型,用于在 Windows 客户端、服务器以及移动或嵌入式设备上构建应用程序。开发人员使用 .NET 构建各种类型的应用程序:Web 应用程序、服务器应用程序、智能客户端应用程序、控制台应用程序、数据库应用程序等等。”

维基百科的定义是:“Microsoft .NET Framework 是包含在 Microsoft Windows 操作系统中的软件组件。它提供了大量预先编码的解决方案来满足常见的软件开发需求,并管理为框架专门编写的程序的执行。.NET Framework 旨在被 Windows 平台上创建的大多数新应用程序使用。”

微软定义的问题在于它提到了“托管代码编程模型”,这仍然是微软的术语。对此最好的定义是:“托管代码是指其执行由 .NET Framework 公共语言运行时管理的代码”(参见 Brad Adams 在 MSDN 上的博客)。

维基百科的定义指出了从开发人员的角度来看的两个更重要的方面

  • 存在一个庞大的类库集合,可以完成所有常见的编程任务。除了这些类库的庞大规模,它们还在快速发展。
  • 执行环境是特定于框架的。术语“虚拟机”通常用于描述这种环境。

这两个主要特征与 Java 环境类似,Java 环境是 .NET 框架的主要竞争对手。本模块旨在帮助你学习 .NET 框架。它不会涉及 .NET 与 Java 的比较和讨论。

框架结构

公共语言基础设施 (CLI) 的可视概述

右侧的图片来自维基百科关于 .NET 框架的文章(见上文)。

它描述了用 .NET 兼容语言编写的程序从源代码到执行的过程。与传统编程语言(例如 C++)的区别在于程序要编译两次。第一次编译是从原始语言编译到“通用中间语言”(CIL)。这实际上是“进入”程序集的内容。

第二次编译是从 CIL 编译到机器代码。这种编译通常由“即时”(JIT)编译器自动执行。也可以使用原生映像生成器 (ngen) 实用工具在运行时之前创建机器代码。

这种架构对本书的主题有许多直接的影响。其中包括

  • 第二次编译的“源代码”(CIL 代码)始终在运行时可供虚拟机使用。这意味着可以在运行时轻松分析此代码,与传统的编译环境相反。这个特性是平台反射功能的基础。我们不仅可以分析“中间源代码”,还可以实际在运行时创建(发出)一些代码,然后进行“即时”编译和执行。
  • 执行在 CLR 的上下文中完成(这就是“托管”代码的概念)。换句话说,我们可以说运行时始终“知道”它要发送执行的内容,与传统环境不同,在传统环境中,代码直接由操作系统执行。这意味着你可以告诉运行时执行或不执行某种类型的代码。这是代码访问安全的依据。
  • .NET 语言(C#、VB 等)的大多数功能都与中间语言的功能直接相关。换句话说,大多数情况下,第一次编译非常简单。但是,有些结构在 CIL 中没有直接的等价物(例如,C# 中的属性在 CIL 中映射到类方法)。

我们可以继续这样讨论很长时间。我们想在这里说明的是,开发人员可以从对平台的详细了解中获益良多,即使它没有直接被列为考试目标。

如果你想在微软文档中阅读有关框架的信息,可以查看 MSDN

最后我们要指出的是,公共语言运行时 (CLR) 可以执行在不同的上下文中

  • ASP.NET 用于 Web 应用程序,它直接链接到 Internet 信息服务 (IIS)。
  • Internet Explorer 用于客户端 Web 控件
  • 在用于控制台、服务和 Windows 应用程序的独立主机中。

默认情况下,本书中的示例将使用独立的可执行文件。这只是因为对于非常简单的程序,它们更容易部署。这并不意味着 Windows 应用程序比 Web 应用程序更可取。

本书的定位

.NET 堆栈

右侧的图片,也来自维基百科,简化但清晰地展示了框架的功能组件。

本书(以及相关的考试)涉及“基类库”组件和程序与“公共语言运行时”组件的基本关系。

本书不涵盖 ASP.NET、ADO.NET、WF 或任何其他更专业的组件。

我们可以说,我们广泛地涵盖了基础知识,但浅显地涵盖了框架本身。

与公共语言运行时的关系包括以下内容

  • 部署
  • 安全
  • 配置
  • 反射
  • 服务
  • 与 WMI 的关系
  • 互操作性
  • 等等。

基类库包括

  • 类型
  • 集合
  • 泛型
  • 文本操作
  • 基本绘图
  • 基本全球化
  • 序列化
  • 基本输入/输出
  • 多线程
  • 等等。

总而言之,它们都是比较“枯燥”的主题。从广泛地涵盖基础知识开始,这对于初学者来说肯定不是最有趣的学习模式。因此,我们建议真正的初学者从更合适的资料开始。

关于框架“堆栈”的最后一点是,新版本倾向于添加新的组件,而不是“重做”现有的组件。这意味着大多数“2.0”基础知识在“3.5”中仍然有效。然而,一些“基本”功能在新版本中得到了增强或更改。我们会尽量在可能的地方将这些内容作为旁注记录下来。

编程范式

为了简单起见(过于简单?),我们可以说编程范式是编程的“风格”,是建模问题并将其解决方案“翻译”成代码的一种特定方式。

从开发人员的角度来看,.NET 框架(及其类库)是一个通用的面向对象平台,它不断扩展以支持其他编程范式(使用泛型进行泛型编程,使用属性进行面向方面编程,使用 WF 进行响应式编程,使用 C# 3.0 中的 Lambda 表达式进行函数式编程等)。这种“扩展过程”反映了 Java 的扩展过程。.NET 和 Java 平台是目前唯一支持这种广泛的多范式扩展过程的两个平台。

讨论每个范式实现的范围和“理论健全性”显然超出了本书的范围。

我们在这里的观点是,在同一个平台上浏览所有这些编程风格可能会让人感到困惑。维护代码也变得越来越困难,因为两个最初的程序员可能会使用截然不同的“风格”或结构来解决同一个问题。

因此,我们将尽可能地将不同的 .NET 概念与其各自的“风格”联系起来,为读者提供一些背景信息。

程序集

根据 MSDN,程序集是:“.NET 框架应用程序的构建块;它们构成了部署、版本控制、重用、激活范围和安全权限的基本单元”。

.NET 框架现在是微软在 Windows 平台上开发应用程序的首选方式。在这方面,它取代了组件对象模型 (COM)。这很重要,因为尽管存在一些部署和管理问题(还记得 DLL Hell 吗?),但 COM 在基于组件的计算(重用整个可执行组件,而不仅仅是程序代码)的发展中发挥了重要作用。人们投入了大量精力将 COM 的基于组件的概念移植到 .NET 框架中,本书的很大一部分内容都涉及这些概念(安全性、安装、版本控制等)。

程序集是 COM 组件的继承者。


Clipboard

待办事项
这里将列出程序集主要特征的简短列表。


系统类型和集合

考试目标:开发使用系统类型和集合的应用程序。


主题

程序、对象和方法

.NET 本质上是一组用于构建和执行计算机程序的工具。计算机程序是提供给计算机的一组指令,这些指令会自动执行这些指令以操作某些数据。.NET 遵循面向对象范式,这意味着指令和数据围绕表示事物或概念的“对象”进行分组。共享相同指令并操作相同类型数据的对象被分组到同一类型中。

面向对象程序是一系列类型的定义。对于每种类型,都会指定数据类型和指令。指令被分组到方法中。可用的指令之一是创建已定义类型的一个对象。另一种指令是请求执行与对象关联的方法。这称为调用方法。调用方法时,会执行其指令,直到方法终止(返回)。方法返回后,调用(调用)方法会执行其下一条指令(语句)。

启动程序执行时,系统会创建一个第一个对象并调用该对象的方法(这称为main方法)。在该方法中,通常会创建其他对象并调用这些对象的方法。这些方法将调用其他方法,创建其他对象,依此类推。随着被调用的方法返回,'’main’’方法最终会完成,标记程序执行的结束。

系统类型

对于经验丰富的面向对象开发人员来说,本节内容可能显而易见,但考试中的一些具体目标与类型系统直接相关。

类型是分类语言概念或对象的一种方式。这种分类的组织方式称为“类型系统”。类型系统也可以以不同的方式对类型本身进行分类。

在 .NET 中对类型进行分类的第一种方式是在框架类库中的类型(系统类型)和开发人员构建的类型(自定义类型)之间进行区分。

编写面向对象程序可以看作是定义一个或多个自定义类型的过程。然后将这些类型打包到某种执行单元中(在 .NET 的情况下为程序集)。程序集被编译然后执行,从某个入口点开始,该入口点将是自定义类型之一的指定方法。

这些自定义类型使用

  • 系统类型来执行“预先编程”的指令序列
  • 其他自定义类型

系统类型也打包在程序集中。自定义程序集必须引用系统程序集才能使用系统类型。

在 .NET 中,还有其他方法对类型进行分类。其中之一是根据基于这些类型创建的对象映射到计算机内存的方式。这将使我们得到值类型和引用类型。

另一种方式是通过反射类别(类、值类型、接口、泛型等)。

另一种方法是区分运行时直接支持的类型(内置类型)与在类库或自定义类型中定义的类型。

这些类别也可以相互交叉,这将使我们获得诸如“内置值类型”或“系统接口”之类的概念。当遇到这种组合时,请注意使用的分类。

命名空间是组织类型的另一种方式,这样可以更轻松地找到它们。请参阅 此处,了解有关命名空间的讨论。

在命名空间的上下文中,系统类型是包含在 System 命名空间或其子命名空间之一中的类型,自定义类型(非系统类型)应使用其他命名空间。

要了解微软如何描述 .NET 类型系统,请参阅 MSDN。然后,要概述类库(系统类型),请参阅 MSDN

事实上,考试的大部分内容都基于如何使用类型库(系统类型)的常见部分。这就是为什么考试目标列表(以及本书的目录)如此之长的原因。

Hello World

对于 .NET 的新手,您可能需要稍作休息,了解到目前为止讨论的概念是如何在非常简单的示例中使用的。接下来的概念并不那么简单。我们将在此处提供一个这样的示例 此处

值类型

值类型表示类型系统中值/引用分类的一部分。

值类型的实例直接包含其数据(值)。例如,Int32 局部变量的内存直接分配在堆栈上。

值类型本身分为 3 类

  • 内置值类型
  • 用户定义的值类型
  • 枚举

请记住,内置类型是运行时直接支持的类型。

它们是任何程序的构建块,因为它们是机器指令最终作用的对象。其余的本质上是对这些类型的组合。

除 Object 和 String 外,所有内置类型都是值类型。

内置值类型包括

  • 整数类型(Byte、SByte、Int16、Int32、Int64、UInt16、UInt32 和 UInt64)
  • 浮点类型(Single 和 Double)
  • 逻辑类型(Boolean)
  • 其他类型(Char、Decimal、InPtr 和 UInPtr)

所有内置值类型都在 System 命名空间中定义(例如 System.Int32),并且在 VB 和 C# 中具有表示它们的关键字(例如 C# 中的 int、VB.NET 中的 integer),但 InPtr 和 UInPtr 除外。

旁注

我们在上面提到过,类型分类有时可能很混乱。例如,请注意 System.DateTime 在 培训工具包(第 5 页)中被呈现为内置值类型,并且这在 MSDN KB 中未被识别为错误。根据 官方规范(第 19 页),它不是内置类型。混淆源于 培训工具包 没有明确区分系统类型(在类库中)和内置类型(运行时直接处理的基本类型)。

这里的要点不是吹毛求疵或贬低培训工具包作者所做的工作。我们只是想指出,在开始编写代码之前,花几分钟时间明确区分这些概念可能是值得的。

所有值类型都直接从 System.ValueType 派生(对于内置类型和用户定义类型),或者间接通过 System.Enum 从 System.ValueType 派生(对于枚举)。

枚举是一种命名一组底层整数类型(带符号或无符号)值的方式。作为整数类型的限制,它们充当其底层类型。

用户定义的值类型和枚举都包含系统类型和自定义类型。

System 用户定义值类型的示例是 System.Drawing.Point,它用于绘图。

您可以使用特定语言结构(C# 中的 struct、VB.NET 中的 Structure)构建自定义值类型。

System 枚举的示例是 System.Data.CommandType 枚举,它指定表是文本命令、存储过程调用等。

您可以使用特定语言结构(C# 中的 enum、VB.NET 中的 Enum)构建自定义枚举。

有关使用值类型的示例和说明,请参阅此 部分。另请参阅有关 构建使用 用户定义的值类型的示例。

引用类型

引用类型代表类型系统中值/引用分类的另一部分。

与值类型相反,引用类型的实例不直接包含其数据(值),而是包含对该值内存位置的某种引用。例如,一个字符串局部变量在堆栈上分配内存用于对包含字符串的引用,而不是字符串本身。在这种情况下,字符串本身将分配在堆上并被垃圾回收(稍后会详细介绍)。

两种内置类型被认为是引用类型:对象和字符串,它们也直接在系统库中引用(例如 System.String),并在 .NET 语言中具有自己的构造(例如 C# 中的 string)。

引用类型本身又分为四类:

  • 指针
  • 数组
  • 接口

本书不会讨论指针,因为没有考试目标涉及它们。

接下来的三节将介绍数组、类和接口。

有关值/引用类型的比较,请尝试 这里

有关其他说明和使用引用类型的示例,请参见 这里

数组

数组本质上是一组对象,通常是相同类型的,可以通过索引访问。

有关数组的一般讨论,您可以参阅维基百科文章(在右侧)或访问关于 数据结构 的维基教科书。

数组曾经是编程语言中最常用的特性之一,因为您可以非常高效地从数组中的一个项目跳转到下一个项目(如果您想了解更多关于此的详细信息,可以查看 C 指针和数组)。如今,更多“计算能力”的可用性将重点从数组转移到了集合。数组的两个主要问题是:

  • 它们是固定长度的
  • 它们只支持一种内部组织

集合解决了数组的大多数缺点(尽管存在成本)。数组仍然可以用于必须高效操作的固定组对象。

使用数组 部分,我们将有一些示例。

类本身又分为三类:

  • 用户定义的类
  • 装箱值类型
  • 委托

装箱值类型将在稍后的 装箱/拆箱 部分进行讨论。

委托将在其 部分 中介绍。

用户定义的类是面向对象概念的基本实现。

显然,关于类可以说的很多,但由于没有考试目标涉及它们,因此我们将假设读者已经熟悉该概念并已经使用过它们(在 .NET 或其他地方)。

您可以拥有系统类(数百个)和自定义类(您将在其中编写程序逻辑的基本内容)。

我们有 使用构建 类的示例。

接口

如果您想真正了解面向对象编程到底是什么,只需查看维基百科关于多态性的文章 :-)。

这个想法只是能够对具有“共同点”的不同类型的“共同部分”使用单一类型的引用。

矩形和椭圆都是“形状”,那么我们如何让程序的一部分操作“形状”,而无需了解矩形或椭圆的具体情况。在这种情况下,我们说形状是多态的(从字面上讲,它们可以采用多种形式)。

在面向对象编程中,您可以通过继承获得这种行为。对父类(形状)的引用将毫无问题地操作子对象(矩形或椭圆)。

接口的开发是为了在基于组件的计算环境中获得相同的效果,在该环境中您无法访问源代码,因此无法使用继承。接口构造是 COM 中所有组件通信的基础。

接口是对以明确定义的方式(方法签名)实现一组方法的契约。如果您知道某个类实现了某个接口,那么您就知道可以使用的任何已定义的方法。

从这个定义来看,很容易想象有一个对“接口实例”的引用,您可以从中调用任何接口方法。事实上,框架确实提供了这一点。

现在假设,而不是形状作为矩形的父级,我们只有一个形状接口,它由矩形和椭圆实现。如果我有一个对矩形对象的引用,我将能够将其“转换”为对形状接口的引用,并将其传递给只了解“形状对象”的程序部分。

这与我们通过继承获得的多态行为完全相同。

类库严重依赖接口,对该概念的清晰理解至关重要。

与继承相比,接口的一个有趣问题是您不会获得默认实现(虚拟父方法),因此您必须重新编写所有内容。这里确实存在技术和工具,但总是需要做一些额外的工作。更多信息将在泛型中介绍…

我们有 使用构建 接口的示例。 标准接口 部分展示了 System 命名空间的一些接口。

属性

现在,我们简短地讨论一下类型,谈谈属性。首先,属性不是类型。它们是添加到程序元素(程序集、类、方法等)中的信息元素,以在与该元素关联的普通代码之外对其进行限定。

这些添加的“属性”用于对底层程序元素进行操作,而无需修改元素的执行逻辑。

我们为什么要在执行代码之外操作程序元素?简而言之,面向对象的概念难以处理所谓的横切关注点,或者是对所有或大多数类适用的程序方面。此类方面的示例包括安全性、持久性、序列化等。与其修改每个类以添加序列化逻辑(这与业务规则无关),不如向类添加序列化属性来指导这些类的序列化过程,而无需更改业务逻辑(执行代码)。

框架的许多功能依赖于属性来向程序元素添加信息。属性由编译过程保留,并在程序集中与其限定元素关联。它们在运行时使用反射以编程方式进行分析,以调整“横切”功能的行为。

与类型一样,我们有系统属性(框架的一部分)和自定义属性(由开发人员构建)。自定义属性的开发将在 反射部分 中介绍。在此之前,我们将仅限于系统属性,重点介绍它们的定义以及它们如何确定框架的行为。

属性使用部分 将提供一些关于如何使用简单系统属性的示例。

在此,我们要注意,属性不是向程序添加“非执行”信息以修改其行为的唯一方法。例如,XML 配置文件可用于指定将影响程序执行的参数。我们将在本书的配置部分讨论这些内容。

有关 C# 中属性的讨论,请参见 MSDN

集合

集合是对象的集合。框架具有许多类型的集合,涵盖了您将对象作为组处理的大多数情况。了解这些集合类型可以节省您“重新编码”等效逻辑的时间,并使您的程序更易于维护。

与数组不同,集合有多种形式,每种形式都有其特定的内部组织。每种类型的集合都与特定类型的问题相关联。当我们提供每种类型的集合的示例时,我们将指出问题类型。

我们有两个包含集合示例的部分:

集合的主要缺点是,在“现实生活中”,分组在一起的对象通常具有某些共同特征(它们是相同类型,具有共同的父类型或支持共同的接口)。因此,在大多数情况下,我们对对象“了解”的不仅仅是它们的组织方式。集合不允许我们使用这些知识来验证传递给集合的对象或适用于集合中所有对象的代码逻辑(不进行强制转换和异常处理)。

框架的 2.0 版本引入了泛型集合。它们在保留集合其他优势的同时解决了这个问题。因此,应尽可能使用它们,而不是“普通”集合。

有关集合的一些外部链接是 GotDotNetAspNetResources

有关数组、集合和数据结构的总体讨论,请参阅 MSDN

泛型

泛型编程或参数化类型的使用不是面向对象的概念。从这个意义上说,泛型有点像属性,它们被添加到主流面向对象平台中,以解决面向对象技术难以轻松处理的情况。

要开始我们的讨论,请阅读以下有趣的 链接文章(附件),该文章介绍了泛型集合中泛型的概念。此外部 教程 也做了同样的事情。

泛型的概念很有趣,因为它将泛化的概念应用于类型。类型本身是对对象(面向对象编程的基础)的泛化。因此,我们开始操作类型,即以类型作为参数。

当您将参数类型替换为特定类型时(例如,当您声明该类型的变量时),将获得实际类型。

// C#
List<int> myIntList = new List<int>()

'// VB.NET
Dim myIntList As New List(Of Integer)

在 .NET 框架中,这种替换是在第二次编译(从 CIL 到机器代码的即时编译)期间完成的。换句话说,泛型由 CIL 支持,因此是框架本身的一部分,而不是所用语言的一部分(例如 C#)。

有关泛型的更多信息,请参阅 MSDN

我们有 使用构建 泛型类型的示例。

有关泛型集合的示例,请参阅 使用泛型集合 部分。

异常

这是抛出一般异常的方式

// C#
throw new Exception("This is an exception.");

'// VB.NET
Throw New Exception("This is an exception.")

这是处理异常的方式

// C#
try
{
   throw new Exception("This is an exception.");
}
catch (Exception e)
{
  Console.WriteLine(e.Message);
}

'// VB.NET
Try
  Throw New Exception("This is an exception.")
Catch ex As Exception
  Console.WriteLine(ex.Message)
End Try

事件和委托

有关事件和委托的“官方”讨论,请参阅 MSDN。我们将在这里对所涉及的许多概念进行更一般的讨论。

第一个概念是委托或将类功能的一部分“委托给”程序中“其他地方”的想法。委托的重要好处是“委托”对象不必知道委托功能是如何实现的。一种“委托”的方式是定义接口。如果一个对象对接口有一个引用,它可以将部分功能委托给实现该接口的对象,而无需了解该对象的大量信息。

.NET 2.0 定义了另一种称为委托的引用类型,它以稍微不同的方式实现委托模式。委托是一种类型,它定义对必须具有与委托定义相同签名的单个函数的引用。签名是对函数参数类型及其返回类型的描述。与类一样,您可以创建该类型的对象。创建的对象是对函数的引用,该函数可以被分配(将引用设置为特定函数),作为参数传递或执行(实际执行被引用的函数)。与接口不同,委托只定义一个函数,并且可以直接创建委托实例(无需拥有实现接口的另一个类)。

.NET 中的大多数委托都派生自多播委托。多播委托是一个委托,它保留对具有与委托定义相同签名的函数的引用列表,而不是单个引用。可以使用 += 运算符将引用添加到多播委托。当您执行多播委托时,每个引用的函数都会依次执行。

如果一个对象来自实现多播委托成员的类,那么如果您对该对象有一个引用,您可以将对您选择的函数的引用“添加到”多播委托中。如果对象决定“执行”其委托成员,那么您添加了引用的函数将被执行。

这个多播委托成员正是 .NET 中的事件。这就是为什么事件和委托几乎总是被一起讨论的原因。

定义事件的步骤是

  • 您将委托类型定义为对具有特定签名的函数的引用
  • 您将事件成员添加到类并将其与您刚刚定义的委托类型关联,这将实例化与事件关联的多播委托
  • 当您对类的对象的引用时,您可以将对任何与委托类型具有相同签名的方法的引用添加进去。
  • 当对象引发事件时,关联的多播委托将被执行,从而触发对引用的函数的执行。

大多数时候,您将添加对您自己方法的引用。当引用的对象触发其事件时,它实际上会执行您的方法之一,而无需知道它。此设置是发布/订阅模式的实现。您订阅事件,表示您希望在发生特定“事件”时被告知。许多对象可以订阅同一个事件。当引用的对象触发其事件时,它会“发布”一条消息,说明事件已以对所有“注册”函数的函数调用的形式有效地发生。

因此,任何类都可以定义事件。许多系统类定义事件来向您的代码传达“执行环境中发生了某些事情”(鼠标移动、按下键、在套接字上收到消息等)的事实。构建一个“等待”事件发生并对其做出反应的程序称为事件编程。大多数在线应用程序和服务都遵循这种设计。我们将在多线程部分更详细地讨论捕获系统事件的方式。

有关事件和委托的示例,请参阅 本节


类、接口和工具

Hello World 示例

    using System;
    using System.Collections.Generic;
    using System.Text;
namespace HelloWorldLab1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello world!");
            Console.WriteLine("Press ENTER to exit");
            Console.ReadLine();
        }
    }
}

注意:在 Visual Studio 中创建新的控制台项目时,默认情况下会生成所有代码,除了 Main 方法中的 3 个“控制台”行。只需添加这 3 行并运行程序。

我们已经讨论了该程序的所有部分,除了 Console 的作用,这将在输入/输出部分(流)中讨论。现在,假设它是一个 System 类,在 System 命名空间中(“using System”指令使“System.”部分成为“System.Console.Writeline()”可选),以及 WriteLine() 和 ReadLine() 方法写入并读取字符串到控制台和从控制台中读取字符串。



使用系统类型

考试目标:通过使用 .NET Framework 2.0 系统类型来管理 .NET Framework 应用程序中的数据。

(参考 System 命名空间)

值类型用法

以下是一些关于值类型的“用法”导向的说明。

值类型包含它们被分配的值

int a = 1;  // the variable "a" contains "1" of value type int

值类型也可以通过使用 new 关键字来创建。使用 new 关键字使用从类型默认构造函数获得的默认值初始化变量

int a = new int(); // using the default constructor via the new keyword
return a;          // returns "0" in the case of type Int.

值类型可以在未初始化的情况下声明,但必须在使用之前初始化为某个值

int a;     // This is perfectly acceptable
return a;  // NOT acceptable!  You can't use "a" because "a" doesn't have a value!

值类型不能等于 null。.NET 2.0 提供了 可空类型 来解决此限制,将在 下一节 中讨论,但 null 不是值类型中的有效值

int a = null;  // Won't compile - throws an error.

如果将值类型复制到另一个值类型,则值将被复制。更改副本的值不会影响原始值的价值。第二个仅仅是第一个的副本 - 赋值后它们没有任何联系。这是相当直观的

int var1 = 1;
int var2 = var1;  //the value of var1 (a "1" of type int) is copied to var2
var2 = 25;        // The "1" value in var2 is overwritten with "25"
Console.WriteLine("The value of var1 is {0}, the value of var2 is {1}", var1, var2);

这将导致以下输出

The value of var1 is 1, the value of var2 is 25

更改副本的值(在本例中为var2)不会影响原始值的价值(var1)。这与引用类型不同,引用类型复制对值的引用,而不是值本身。

值类型不能派生。

值类型作为方法参数默认情况下按值传递。值类型的副本被创建,并且副本作为参数传递给方法。如果在方法内部更改了参数,则不会影响原始值类型的价值。


Clipboard

待办事项
最终,我们可以添加一个示例来展示使用一些内置值类型(整数、浮点数、逻辑、字符和小数)以及值类型的值参数传递的方式。


可空类型

参阅 MSDN

可空类型…

  • 是泛型类型
  • 是 System.Nullable 结构的实例。
  • 只能在值类型上声明。
  • 使用 System.Nullable<type> 或简写 type? 声明 - 这两种方式可以互换。
System.Nullable<int> MyNullableInt;  // the long version 
int? MyNullableInt;                  // the short version
  • 接受底层类型的正常值范围,以及 null
bool? MyBoolNullable;  // valid values: true || false || null

小心使用 可空 布尔值!在 if、for、while 或逻辑评估语句中,可空布尔值将 null 值等同于 false - 它不会抛出错误。

方法:T GetValueOrDefault() & T GetValueOrDefault(T defaultValue)
返回存储的值或默认值(如果存储的值设置为 null)。

属性:HasValue & Value
可空类型有两个只读属性:HasValueValue

HasValue 是一个布尔属性,如果 Value != null,则返回 true。它提供了一种在使用类型之前检查类型是否为非 null 值的方法,以防止出现错误

 int? MyInt = null;
 int MyOtherInt;
 MyOtherInt = MyInt.Value + 1;    // Error! You can't add null + 1!!
 if (MyInt.HasValue) MyOtherInt = MyInt.Value + 1; // This is a better way.

返回您的类型的值,null 或其他。

int? MyInt = 27;
if (MyInt.HasValue) return MyInt.Value;  // returns 27.
MyInt = null;
return MyInt; // returns null.

包装/解包

包装是将非空类型N 中的值m 通过表达式new N?(m)打包到可空类型N?中的过程。

解包评估可空类型N?的实例m 作为类型N 或 NULL 的过程,并通过“值”属性执行(例如m.Value)。

注意:解包空实例会生成异常System.InvalidOperationException

?? 操作符(也称为空合并操作符)

虽然不是专门用于可空类型,但??操作符在您想要使用默认值而不是null值时非常有用。??操作符返回语句的左操作数(如果非空),否则返回右操作数。

int? MyInt = null;
return MyInt ?? 27;  // returns 27, since MyInt is null

有关更多信息,请参阅R. Aaron Zupancic 关于 ?? 操作符的博客文章

构建值类型

构建值类型必须非常简单。以下示例定义了一个自定义“点”结构,它只有 2 个双精度成员。有关将值类型隐式转换为引用类型的讨论,请参阅装箱和拆箱

C# 代码示例

构建和使用自定义值类型(结构)

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace ValueTypeLab01
   {
       class Program
       {
           static void Main(string[] args)
           {
               MyPoint p;
               p.x = 3.2;
               p.y = 14.1;
               Console.WriteLine("Distance from origin: " + Program.Distance(p));
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // method where MyPoint is passed by value
           public static double Distance(MyPoint p)
           {
               return Math.Sqrt(p.x * p.x + p.y * p.y);
           }
       }
       // MyPoint is a struct (custom value type) representing a point
       public struct MyPoint
       {
           public double x;
           public double y;
       }
   }

使用用户定义的值类型

以上示例可以在这里使用。请注意,p 变量不必用new运算符初始化。

使用枚举

以下示例展示了 System 枚举 DayOfWeek 的简单用法。该代码比测试表示一天的整数值要简单得多。请注意,对枚举变量使用 ToString() 将给出值的字符串表示(例如“星期一”而不是“1”)。

可以使用反射列出可能的值。有关详细信息,请参阅该部分。

有关 Enum 类讨论,请参阅MSDN

有一种特殊类型的枚举,称为标志枚举。考试目标没有专门提到它。如果您有兴趣,请参阅MSDN

C# 示例

枚举的简单使用

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace EnumLab01
   {
       class Program
       {
           static void Main(string[] args)
           {
               DayOfWeek day = DayOfWeek.Friday;
               if (day == DayOfWeek.Friday)
               {
                   Console.WriteLine("Day: {0}", day);
               }
               DayOfWeek day2 = DayOfWeek.Monday;
               if (day2 < day)
               {
                   Console.WriteLine("Smaller than Friday");
               }
               switch (day)
               {
                   case DayOfWeek.Monday:
                       Console.WriteLine("Monday processing");
                       break;
                   default:
                       Console.WriteLine("Default processing");
                       break;
               }
               int i = (int)DayOfWeek.Sunday;
               Console.WriteLine("Int value of day: {0}", i);
               // Finishing
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
   }

构建枚举

构建自定义枚举非常简单,如下例所示。

C# 示例

声明简单枚举

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace EnumLab02
   {
       class Program
       {
           public enum MyColor
           {
               None = 0,
               Red,
               Green,
               Blue
           }
           static void Main(string[] args)
           {
               MyColor col = MyColor.Green;
               Console.WriteLine("Color: {0}", col);
               // Finishing
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
   }

使用引用类型

引用类型更常被称为对象接口委托都是引用类型,以及内置的引用类型System.ObjectSystem.String。引用类型存储在托管堆内存中。

与值类型不同,引用类型可以被赋值为null

复制引用类型会复制指向该对象的引用,而不是该对象的副本本身。这有时会让人觉得很奇怪,因为更改引用副本也会更改原始引用。

值类型存储它被赋值的值,简单明了——但引用类型存储指向内存中位置的指针(在堆上)。将堆视为一堆储物柜,而引用类型持有储物柜编号(在这个比喻中没有锁)。复制引用类型就像给某人一份储物柜编号的副本,而不是其内容的副本。指向同一内存的两个引用类型就像两个人共用同一个储物柜——两个人都可以修改其内容

C# 代码示例

使用引用类型的示例

public class Dog
{
  private string breed;
  public string Breed { get {return breed;} set {breed = value;} }
  
  private int age;
  public int Age { get {return age;} set {age = value;} }
  
  public override string ToString()
  {
    return String.Format("is a {0} that is {1} years old.", Breed, Age);
  }
  
  public Dog(string dogBreed, int dogAge)
  {
    this.breed = dogBreed;
    this.age = dogAge;
  }
}

public class Example()
{
   public static void Main()
   {
     Dog myDog = new Dog("Labrador", 1);    // myDog points to a position in memory.
     Dog yourDog = new Dog("Doberman", 3);  // yourDog points to a different position in memory.

     yourDog = myDog; // both now point to the same position in memory, 
                    // where a Dog type has values of "Labrador" and 1
   
     yourDog.Breed = "Mutt";
     myDog.Age = 13; 

     Console.WriteLine("Your dog {0}\nMy dog {1}", yourDog.ToString(), myDog.ToString());
   }
}

由于 yourDog 变量和 myDog 变量都指向同一个内存存储,因此其输出将是

Your dog is a Mutt that is 13 years old.
My dog is a Mutt that is 13 years old.

作为操纵引用类型的练习,您可能想要使用String 和 StringBuilder类。我们将这些与文本操作部分放在一起,但操纵字符串几乎所有程序的基本操作。

使用和构建数组

请参阅MSDN以获取参考信息。

使用类

构建自定义类

使用接口

构建自定义接口

使用特性

使用泛型类型

System 泛型类型的四大类别的使用将在本书的其它部分中进行演示。

  • 可空类型已在上面讨论过。
  • 之后将有一整节内容介绍泛型集合。
  • 将在事件/委托部分讨论泛型事件处理程序。
  • 泛型委托也将讨论在事件/委托部分以及泛型集合部分(比较器类)。

如果您在 Visual Studio 中复制下一个非常简单的示例并尝试向列表中添加除 int 以外的任何内容,程序将无法编译。这展示了泛型的强类型能力。

泛型的简单使用(C#)

非常简单的泛型使用

   using System;
   using System.Collections.Generic;
   namespace GenericsLab01
   {
       class Program
       {
           static void Main(string[] args)
           {
               List<int> myIntList = new List<int>();
               myIntList.Add(32);
               myIntList.Add(10); // Try to add something other than an int 
                                  // ex. myIntList.Add(12.5);
               foreach (int i in myIntList)
               {
                   Console.WriteLine("Item: " + i.ToString());
               }
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
   }

您可以使用 List<string> 而不是 List<int>,您将以同样的价格获得字符串列表(您正在使用相同的 List(T) 类)。

构建泛型

文章中展示了自定义泛型集合的编程,该文章在主题讨论中提到。

这里我们有一个泛型函数的示例。我们使用交换两个引用的简单问题。虽然非常简单,但我们仍然看到了泛型的基本优势。

  • 我们不必为每种类型重新编写一个交换函数。
  • 泛化不会让我们失去强类型(尝试交换 int 和字符串,它不会编译)。
简单的自定义泛型函数(C#)

简单的自定义泛型函数

   using System;
   using System.Collections.Generic;
   using System.Text;
   namespace GenericsLab03
   {
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Swap strings
               string str1 = "First string";
               string str2 = "Second string";
               pgm.swap<string>(ref str1, ref str2);
               Console.WriteLine(str1);
               Console.WriteLine(str2);
               // Swap integers
               int int1 = 1;
               int int2 = 2;
               pgm.swap<int>(ref int1, ref int2);
               Console.WriteLine(int1);
               Console.WriteLine(int2);
               // Finish with wait
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // Swapping references
           void swap<T>(ref T r1,ref T r2)
           {
               T r3 = r1;
               r1 = r2;
               r2 = r3;
           }
       }
   }


下一步是展示一个示例,包括泛型接口、实现该泛型接口的泛型类以及从该泛型类派生的类。该示例还使用接口和派生约束。

这是另一个简单的涉及员工和供应商的问题,它们除了可以向“付款处理程序”请求付款外,别无共同之处(请参阅访问者模式)。

问题是,如果您需要对特定类型的付款进行特定处理(仅针对员工),那么应该将逻辑放在哪里。有无数种方法可以解决这个问题,但使用泛型使得以下示例变得干净、明确且强类型。

另一个好处是,它与容器或集合无关,您将在其中找到几乎所有泛型示例。

请注意,EmployeeCheckPayment<T> 类派生自 CheckPayment<T>,对类型参数 T 施加了更强的约束(必须是员工,而不仅仅是实现 IPaymentInfo)。这让我们有机会在 RequestPayment 方法中访问所有付款逻辑(来自基类)以及所有员工公共接口(通过 sender 方法参数),而且无需进行任何强制转换。

自定义泛型接口和类(C#)

自定义泛型接口和类

   using System;
   using System.Collections.Generic;
   using System.Text;
   namespace GennericLab04
   {
       class Program
       {
           static void Main(string[] args)
           {
               // Pay supplier invoice
               CheckPayment<Supplier> checkS = new CheckPayment<Supplier>();
               Supplier sup = new Supplier("Micro", "Paris", checkS);
               sup.InvoicePayment();
               // Produce employee paycheck
               CheckPayment<Employee> checkE = new EmployeeCheckPayment<Employee>();
               Employee emp = new Employee("Jacques", "Montreal", "bigboss", checkE);
               emp.PayTime();
               // Wait to finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
       // Anything that can receive a payment must implement IPaymentInfo
       public interface IPaymentInfo
       {
           string Name { get;}
           string Address { get;}
       }
       // All payment handlers must implement IPaymentHandler
       public interface IPaymentHandler<T> where T:IPaymentInfo 
       {
           void RequestPayment(T sender, double amount);
       }
       // Suppliers can receive payments thru their payment handler (which is given by an  object factory)
       public class Supplier : IPaymentInfo
       {
           string _name;
           string _address;
           IPaymentHandler<Supplier> _handler;
           public Supplier(string name, string address, IPaymentHandler<Supplier> handler)
           {
               _name = name;
               _address = address;
               _handler = handler;
           }
           public string Name { get { return _name; } }
           public string Address { get { return _address; } }
           public void InvoicePayment()
           {
               _handler.RequestPayment(this, 4321.45);
           }
       }
       // Employees can also receive payments thru their payment handler (which is given by an  object factory)
       // even if they are totally distinct from Suppliers
       public class Employee : IPaymentInfo
       {
           string _name;
           string _address;
           string _boss;
           IPaymentHandler<Employee> _handler;
           public Employee(string name, string address, string boss, IPaymentHandler<Employee> handler)
           {
               _name = name;
               _address = address;
               _boss = boss;
               _handler = handler;
           }
           public string Name { get { return _name; } }
           public string Address { get { return _address; } }
           public string Boss { get { return _boss; } }
           public void PayTime()
           {
               _handler.RequestPayment(this, 1234.50);
           }
       }
       // Basic payment handler
       public class CheckPayment<T>  : IPaymentHandler<T> where T:IPaymentInfo
       {
           public virtual void RequestPayment (T sender, double amount) 
           {
               Console.WriteLine(sender.Name);
           }
       }
       // Payment Handler for employees with supplementary logic
       public class EmployeeCheckPayment<T> : CheckPayment<T> where T:Employee
       {
           public override void RequestPayment(T sender, double amount)
           {
               Console.WriteLine("Get authorization from boss before paying, boss is: " + sender.Boss);
	            base.RequestPayment(sender, amount);
           }
       }
   }

异常类

一些指向 MSDN 的链接

  • 异常和异常处理 - MSDN
  • 处理和抛出异常 - MSDN
  • 异常层次结构 - MSDN
  • 异常类和属性 - MSDN

装箱和拆箱

请参阅MSDN

所有类型直接或间接地从 System.Object 派生(包括值类型,通过 System.ValueType)。这允许对“任何”对象的引用非常方便的概念,但会带来一些技术问题,因为值类型没有被“引用”。装箱和拆箱随之而来。

装箱和拆箱使值类型能够像对象一样对待。装箱值类型将它打包到 Object 引用类型的实例中。这允许值类型存储在垃圾回收堆上。拆箱从对象中提取值类型。在此示例中,整型变量 i 被装箱并赋值给对象 o

int i = 123;
object o = (object) i;  // boxing

另请注意,没有必要显式地将整数强制转换为对象(如上面的示例所示)以使整数被装箱。调用它的任何方法也会导致它在堆上被装箱(因为只有装箱形式的对象具有指向虚拟方法表的指针)。

int i=123;
String s=i.toString(); //This call will cause boxing

还有一种第三种方法可以装箱值类型。这发生在您将值类型作为参数传递给期望对象的函数时。假设有一个函数原型为

void aFunction(object value)

现在假设从程序的另一个部分,您像这样调用该函数

int i=123;
aFunction(i); //i is automatically boxed

此调用将自动将整数强制转换为对象,从而导致装箱。

然后可以将对象 o 拆箱并赋值给整型变量 i

o = 123;
i = (int) o;  // unboxing

装箱和拆箱的性能

相对于简单的赋值,装箱和拆箱是计算量很大的过程。当装箱值类型时,必须分配和构造一个全新的对象。在较小程度上,拆箱所需的强制转换在计算上也是昂贵的。

TypeForwardedToAttribute 类

请参阅MSDN

有关 CLR 中 TypeForwardToAttribute 的讨论,请参阅MSDN
其他可能的链接:Marcus 的博客NotGartner


使用集合

考试目标:使用集合管理 .NET Framework 应用程序中一组关联数据。

(参考 System.Collections 命名空间 - MSDN)

ArrayList 类

参见 MSDN

ArrayList 类用于数组,其大小将根据需要动态增加。ArrayList 不一定排序。

using System;
using System.Collections;
public class Demo {
    public static void Main() {
        ArrayList myArrayList = new ArrayList();
        myArrayList.Add("Testing");
        myArrayList.Add("1...2...3");
    }
}
集合接口
ICollection 接口和 IList 接口
ICollection 接口 - MSDN
ICollection 接口是 System.Collections 命名空间中类的基本接口。
ICollection 接口扩展 IEnumerable; IDictionaryIList 是更专门的接口,它们扩展了 ICollectionIDictionary 实现是键值对的集合,如 Hashtable 类。IList 实现是值的集合,其成员可以通过索引访问,如 ArrayList 类。
一些限制对元素访问的集合,例如 Queue 类和 Stack 类,直接实现 ICollection 接口。
如果 IDictionary 接口和 IList 接口都不满足所需集合的要求,则从 ICollection 接口派生新的集合类,以获得更大的灵活性。
以下表格列出了 ICollection 类型公开的成员。
公共属性
  Count - Gets the number of elements contained in the ICollection.  
  IsSynchronized - Gets a value indicating whether access to the ICollection is synchronized (thread safe).  
  SyncRoot - Gets an object that can be used to synchronize access to the ICollection.  

公共方法
  CopyTo - Copies the elements of the ICollection to an Array, starting at a particular Array index.   
IList 接口 - MSDN
IComparer 接口、IEqualityComparer 接口和 IKeyComparer 接口
IComparer 接口 - MSDN
IEqualityComparer 接口 - MSDN
IKeyComparer 接口 - IKeyComparer 在 .Net 2.0 中不存在
IDictionary 接口和 IDictionaryEnumerator 接口
IDictionary 接口 - MSDN
IDictionaryEnumerator 接口 - MSDN
IEnumerable 接口和 IEnumerator 接口 - MSDNMSDN
C# 代码示例

IEnumerator 示例

public class Person
{
   public Person(string fName, string lName)
   {
       this.firstName = fName;
       this.lastName = lName;
   }
   public string firstName;
   public string lastName;
}
public class PeopleEnum : IEnumerator
{
   public Person[] _people;
   //Enumerators are positioned before the first element
   //until the first MoveNext() call.
   int position = -1;
   public PeopleEnum(Person[] list)
   {
       _people = list;
   }
   public bool MoveNext()
   {
       position++;
       return (position < _people.Length);
   }
   public void Reset()
   {
       position = -1;
   }
   public object Current
   {
       get
       {
           try
           {
               return _people[position];
           }
           catch (IndexOutOfRangeException)
           {
               throw new InvalidOperationException();
           }
       }
   }
}
public class People : IEnumerable
{
   private Person[] _people;
   public People(Person[] pArray)
   {
       _people = new Person[pArray.Length];
       for (int i = 0; i < pArray.Length; i++)
       {
           _people[i] = pArray[i];
       }
   }
   public IEnumerator GetEnumerator()
   {
       return new PeopleEnum(_people);
   }
}

写一个处理程序来练习上述代码。

protected void lnkEnumerator_Click(object sender, EventArgs e)
   {
       Person[] peopleArray = new Person[] {
           new Person("Irfan", "Akhtar"),
           new Person("Hammad", "Anwar"),
           new Person("Majid", "Aalim")     };
       PeopleEnum Prson = new PeopleEnum(peopleArray);
  • 使用 IEnumerator 的一种方法。
 while (Prson.MoveNext () )
 {
       Person P = (Person)Prson.Current;
       Response.Write("First Name : " + P.firstName + ", Last Name : " + P.lastName);
 }


  • 使用 IEnumerable 的一种方法。
People peopleList = new People(peopleArray);
foreach (Person p in peopleList)
    Response.Write("First Name : " + p.firstName + ", Last Name : " + p.lastName);
IHashCodeProvider 接口 - MSDN - 该接口现在已过时(从 .NET 2.0 开始)

迭代器

参见 MSDN

迭代器实际上是 IEnumerable 接口的轻量级版本。它主要与 foreach 语句一起使用。
通常,您将实现 IEnumerable 接口的 GetEnumerator 方法。
public class Colors : System.Collections.IEnumerable
{
    string[] colors = { "Red", "Green", "Blue" };
    public System.Collections.IEnumerator GetEnumerator()
    {
        for (int i = 0; i < colors.Length; i++)
        {
            yield return colors[i];
        }
    }
}
这使得可以使用标准 foreach 语句访问该类。类不限于仅实现单个迭代器。可以提供多个迭代器,例如,可以对列表进行升序和降序迭代。要调用命名迭代器,请使用以下语法
foreach (int i in myList.NamedIterator())
{
    System.Console.WriteLine(i);
}
yield 语句标记迭代器执行将在后续迭代中恢复的点。这可用于提供多个 yield 语句
public System.Collections.IEnumerator GetEnumerator()
{
    yield return "Statement returned on iteration 1";
    yield return "Statement returned on iteration 2";
}
要在编程方式结束迭代,请使用
yield break;
语句。

Hashtable 类 - MSDN

用于表示键值对的集合。

CollectionBase 类和 ReadOnlyCollectionBase 类

CollectionBase 类 - MSDN
ReadOnlyCollectionBase 类 -MSDN

DictionaryBase 类和 DictionaryEntry 类

DictionaryBase 类 - MSDN
DictionaryEntry 结构 - MSDN

Comparer 类 - MSDN

Queue 类 - MSDN

SortedList 类 - MSDN

BitArray 类 - MSDN

Stack 类 - MSDN

<noinclide> </noinclude>


使用泛型集合

考试目标:通过使用泛型集合来提高 .NET Framework 应用程序中的类型安全性和应用程序性能。

(参考 System.Collections.Generic 命名空间 MSDN )

Collection.Generic 接口

泛型 IComparable 接口 - MSDN
请注意,IComparable<T> 是 System 命名空间的成员。
当您创建一个类,并且希望该类与支持排序的泛型类型(例如 SortedList<T> 或 List<T>.Sort())一起使用,而不必指定比较器对象时,您将使用此接口。IComparable<T> 的唯一方法是 CompareTo<T>(T other)。MSDN 上有一个示例。
以下示例对自定义的 Point 类实现 IComparable<T>。该示例使用 List<T> 而不是 SortedList<T> 或 OrderedDictionnary<T>,因为比较是基于点到原点的距离进行的,这可能会导致许多点具有相同的距离。
简单使用 IComparable<T>(C#)

简单使用 IComparable<T>(C#)

   using System;
   using System.Collections.Generic;
   using System.Text;
   namespace GenericsLab05
   {
       class Program
       {
           static void Main(string[] args)
           {
               List<Point> lst = new List<Point>();
               lst.Add(new Point(-2, -2));
               lst.Add(new Point(1, 1));
               lst.Add(new Point(2, 2));
               // Sort uses IComparable of Point
               lst.Sort();
               foreach (Point pt in lst)
               {
                   Console.WriteLine(pt.ToString());
               }
               // Wait to finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
       // This is out custom version of a point
       public struct Point : IComparable<Point>
       {
           public double x;
           public double y;
           public Point(double px, double py)
           {
               x = px;
               y = py;
           }
           // Comparaison done based on distance from origin
           public int CompareTo(Point other)
           {
               return Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2)).CompareTo
                   (Math.Sqrt(Math.Pow(other.x, 2) + Math.Pow(other.y, 2)));
           }
           public override string ToString()
           {
               return "(" + x.ToString() + "," + y.ToString() + ")";
           }
       }
   }
泛型 ICollection 接口和泛型 IList 接口
泛型 ICollection 接口 - MSDN
泛型 IList 接口 - MSDN
泛型 IComparer 接口和泛型 IEqualityComparer 接口
泛型 IComparer 接口 - MSDN
泛型 IEqualityComparer 接口 - MSDN
泛型 IDictionary 接口 - MSDN
泛型 IEnumerable 接口和泛型 IEnumerator 接口
泛型 IEnumerable 接口 - MSDN
另请参见 ONDotnet
泛型 IEnumerator 接口 - MSDN
IHashCodeProvider 接口 - MSDN - 该接口现在已过时(从 .NET 2.0 开始)
泛型字典
泛型 Dictionary 类和泛型 Dictionary.Enumerator 结构
泛型 Dictionary 类 - MSDN
泛型 Dictionary.Enumerator 结构 - MSDN
泛型 Dictionary.KeyCollection 类和 Dictionary.KeyCollection.Enumerator 结构
泛型 Dictionary.KeyCollection 类 - MSDN
Dictionary.KeyCollection.Enumerator 结构 - MSDN
泛型 Dictionary.ValueCollection 类和 Dictionary.ValueCollection.Enumerator 结构
泛型 Dictionary.ValueCollection 类 - MSDN
Dictionary.ValueCollection.Enumerator 结构 - MSDN

泛型 Comparer 类和泛型 EqualityComparer 类

泛型 Comparer 类 - MSDN
Comparer<T> 类充当基类,可轻松实现 IComparer<T> 接口。
该示例与 IComparable<T> 相同,只是现在将派生的 Comparer<T> 对象提供给 List<T>.Sort() 方法,而不是在 Point 上实现 IComparable<T> 接口。
这种方法有 2 个优点
  • 即使您无权访问 Point 的源代码,也可以使用它
  • 对于同一个 Point 类,您可以拥有多个派生自 Comparer 的类
自定义 Comparer<T>(C#)

自定义 Comparer<T>(C#)

   using System;
   using System.Collections.Generic;
   using System.Text;
   namespace GenericsLab06
   {
       class Program
       {
           static void Main(string[] args)
           {
               List<Point> lst = new List<Point>();
               lst.Add(new Point(-2, -2));
               lst.Add(new Point(1, 1));
               lst.Add(new Point(2, 2));
               // Sort uses IComparable of Point
               lst.Sort(new DistanceComparer());
               foreach (Point pt in lst)
               {
                   Console.WriteLine(pt.ToString());
               }
               // Wait to finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
       // This is out custom version of a point
       public struct Point 
       {
           public double x;
           public double y;
           public Point(double px, double py)
           {
               x = px;
               y = py;
           }
           public override string ToString()
           {
               return "(" + x.ToString() + "," + y.ToString() + ")";
           }
       }
       // Derive from base comparer class to implement IComparer<T>
       public class DistanceComparer : Comparer<Point>
       {
           public override int Compare(Point p1, Point p2)
           {
               return Math.Sqrt(Math.Pow(p1.x, 2) + Math.Pow(p1.y, 2)).CompareTo
                   (Math.Sqrt(Math.Pow(p2.x, 2) + Math.Pow(p2.y, 2)));
           }
       }
   }
泛型 EqualityComparer 类 - MSDN

泛型 KeyValuePair 结构

参见 MSDN

泛型 List 类、泛型 List.Enumerator 结构和泛型 SortedList 类

泛型 List 类 - MSDN
泛型列表类实例只需使用 List<T> 语法声明,其中 T 是特定类型。
泛型 List.Enumerator 结构 - MSDN
泛型 SortedList 类 - MSDN

泛型 Queue 类和泛型 Queue.Enumerator 结构

泛型 Queue 类 - MSDN
泛型 Queue.Enumerator 结构 - MSDN

泛型 SortedDictionary 类

参见 MSDN
有关 SortedList 和 SortedDictionary 之间的差异,请参见 MSDN

泛型链表

泛型链表表示双向链表,是一种通用链表。它支持枚举器,并实现 ICollection 接口,与 .NET Framework 中的其他类一致。
泛型 LinkedList 类 - MSDN
泛型 LinkedList.Enumerator 结构 - MSDN
泛型 LinkedListNode 类 - MSDN

泛型 Stack 类和泛型 Stack.Enumerator 结构

泛型 Stack 类 - MSDN
泛型 Stack.Enumerator 结构 - MSDN



使用专门的集合

考试目标:通过使用专门的集合来管理 .NET Framework 应用程序中的数据。

(参考 System.Collections.Specialized 命名空间)

专门的字符串类

StringCollection 类 - MSDN
StringDictionary 类 - MSDN
StringEnumerator 类 - MSDN
专门的字典类
HybridDictionary 类 - MSDN
IOrderedDictionary 接口和 OrderedDictionary 类
IOrderedDictionary 接口 - MSDN
OrderedDictionary 类 - MSDN
ListDictionary 类 - MSDN

命名集合

NameObjectCollectionBase 类 - MSDN
NameObjectCollectionBase.KeysCollection 类 - MSDN
NameValueCollection 类 - MSDN

CollectionsUtil 类

CollectionsUtil 类 - MSDN

BitVector32 结构和 BitVector32.Section 结构

BitVector32 结构 - MSDN
BitVector32.Section 结构 - MSDN

标准接口

考试目标:通过实现 .NET Framework 接口,使组件符合标准契约。

(参考 System 命名空间)

IComparable 接口 - MSDN

IComparable 接口定义了一种比较方法,值类型或类实现该方法以创建类型特定的比较方法

IDisposable 接口 - MSDN

IDispose 接口可用于在自定义类中显式释放非托管资源。当不再需要对象时,对象的使用者可以调用此方法。
.Net 垃圾回收器在不再使用托管对象时释放分配给它们的内存,但是,无法预测垃圾回收何时发生,而且它不知道非托管资源,例如窗口句柄或打开的文件和流。

IConvertible 接口 - MSDN

ICloneable 接口 - MSDN

INullableValue 接口 - MSDN

IEquatable 接口 - MSDN

IFormattable 接口 - MSDN



使用事件和委托

考试目标:使用事件和委托控制 .NET Framework 应用程序组件之间的交互。

(参考 System 命名空间)

Delegate 类 - MSDN

委托保存指向一个或多个函数的指针,并在需要时调用它们。
委托的一种常见用途是事件处理。引发事件的类不知道哪些对象或方法想要接收事件,因此在引发事件的对象和接收事件的对象之间需要一个中介或指针机制。委托可以用作函数指针来实现这一点。
委托是一个类,但与普通类不同,它有一个签名。在 .Net 框架中,您只需声明委托,CLR 会处理类的实现。
   //delegate declaration
   public delegate void AlarmEventHandler(object sender,EventArgs e);
第一个完整示例只声明了一个委托类型,然后声明了该类型的变量,将函数分配给它并执行委托变量,这将执行函数。
C# 示例

简单委托

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace DelegateLab01
   {
       // declare the delegate type
       public delegate int IntOperDel(int i);
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Assign the delegate
               IntOperDel deleg = pgm.Increment;
               // Executing the delegate
               int res = deleg(32);
               Console.WriteLine("First value: " + res.ToString());
               // Second assign
               deleg = pgm.Decrement;
               // Second execution
               res = deleg(32);
               Console.WriteLine("First value: " + res.ToString());
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // First function to be assigned to the delegate
           public int Increment(int n)
           {
               return n + 1;
           }
           // Second function to be assigned to the delegate
           public int Decrement(int n)
           {
               return n - 1;
           }
       }
   }
第二个委托示例实现了回调函数的概念。使用委托作为参数调用函数。当它执行委托时,它不知道到底执行了什么函数。它的部分行为委托给作为参数传递的函数(通过委托)。
C# 示例

回调委托

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace DelegateLab02
   {
       // declare the delegate type
       public delegate int IntOperDel(int i);
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Assign the delegate
               IntOperDel deleg = pgm.Increment;
               // Calling a function that will execute de delegate
               // as part of its own logic
               pgm.ExecuteCallBack(deleg);
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // Function to be assigned to a delegate
           public int Increment(int n)
           {
               return n + 1;
           }
           // Function called with a delegate as parameter
           public void ExecuteCallBack(IntOperDel deleg)
           {
               int res = deleg(32);
               Console.WriteLine("Result from executing the callback: " + res.ToString());
           }
       }
   }
第三个委托示例使用委托成员来产生与事件相同的模式。请注意,在没有分配至少一个函数的情况下执行委托成员会导致异常。另外,使用 += 运算符分配两个函数将在执行委托时执行这两个函数。如果委托具有返回值,则将返回最后执行函数的返回值。
C# 示例

委托成员

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace DelegateLab03
   {
       // declare the delegate type
       public delegate void IntOperDel(int i);
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Use += oper to assign functions to the delegate
               pgm.delMember += pgm.Increment;
               pgm.delMember += pgm.Decrement;
               // Calling some member function that will execute the delegate
               pgm.ExecuteSomething();
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // First function to be assigned to a delegate
           public void Increment(int n)
           {
               int res = n + 1;
               Console.WriteLine("Inside increment function: " + res.ToString());
           }
           // Second function to be assigned to a delegate
           public void Decrement(int n)
           {
               int res = n - 1;
               Console.WriteLine("Inside decrement function: " + res.ToString());
           }
           // Class member of the delegate type
           public IntOperDel delMember;
           // Function called to execute the delegate member (raise the "event")
           public void ExecuteSomething()
           {
               this.delMember(32);
           }
       }
   }
第四个示例是事件的基本示例。它与第三个示例完全相同,只是在成员声明中添加了event关键字,并且在调用事件之前进行了测试,因为“空”事件会抛出异常。此示例清楚地表明,事件不过是多播委托。
C# 示例

事件成员

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace DelegateLab04
   {
       // declare the delegate type
       public delegate void IntOperDel(int i);
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Use += oper to assign functions to the delegate
               pgm.delMember += pgm.Increment;
               pgm.delMember += pgm.Decrement;
               // Calling some member function that will execute the delegate
               pgm.ExecuteSomething();
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // First function to be assigned to a delegate
           public void Increment(int n)
           {
               int res = n + 1;
               Console.WriteLine("Inside increment function: " + res.ToString());
           }
           // Second function to be assigned to a delegate
           public void Decrement(int n)
           {
               int res = n - 1;
               Console.WriteLine("Inside decrement function: " + res.ToString());
           }
           // Class member event of the delegate type
           public event IntOperDel delMember;
           // Function called to execute the delegate member (raise the "event")
           public void ExecuteSomething()
           {
               if (this.delMember != null)
                   this.delMember(32);
           }
       }
   }
执行与事件关联的函数称为引发事件。
与事件关联的函数称为事件处理程序

EventArgs 类 - MSDN

按照约定,事件使用一个返回 void 并接受两个参数的委托
  • 一个 System.Object 类型的对象,其中包含对引发事件的对象的引用。
  • 来自从 EventArgs 派生的类的对象,其中包含从引发事件的对象传递到事件处理程序的数据。
EventArgs 类本身不包含任何数据。
因此,没有数据的事件将使用以下形式的委托
public delegate void DelegateTypeName (object sender, EventArgs e)
此简单的事件示例与上一个示例相同,只是使用了 IntEventArgs 类来将 int 参数传递给事件处理程序,并且所有参数都更改为遵循事件的调用约定。
C# 示例

具有遵循调用约定的委托的事件

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace EventLab01
   {
       // the class containing the event data passed to the event handler
       public class IntEventData : EventArgs
       {
           public int IntParm = 0;
           // constructor
           public IntEventData(int i)
           {
               IntParm = i;
           }
       }
       // the delegate type
       public delegate void IntOperDel(object sender, IntEventData e);
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Use += oper to assign functions to the delegate
               pgm.delMember += pgm.Increment;
               pgm.delMember += pgm.Decrement;
               // Calling some member function that will execute the delegate
               pgm.ExecuteSomething();
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // First function to be assigned to a delegate
           public void Increment(object sender, IntEventData e)
           {
               int res = e.IntParm + 1;
               Console.WriteLine("Inside increment function: " + res.ToString());
           }
           // Second function to be assigned to a delegate
           public void Decrement(object sender, IntEventData e)
           {
               int res = e.IntParm - 1;
               Console.WriteLine("Inside decrement function: " + res.ToString());
           }
           // Class member event of the delegate type
           public event IntOperDel delMember;
           // Function called to execute the delegate member (raise the "event")
           public void ExecuteSomething()
           {
               if (this.delMember != null)
                   this.delMember(this, new IntEventData(32));
           }
       }
   }

EventHandler 委托 - MSDNMSDN

System 命名空间中定义了两个特殊的委托,以帮助您进行事件声明。
第一个是 EventHandler 委托。它不传递任何数据。不传递数据的事件可以声明为
public event EventHandler EventName
而无需声明自定义委托。
这是声明不传递数据的事件的常用方法。
C# 示例

具有 EventHandler 委托的事件

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace EventLab03
   {
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Use += oper to assign functions to the delegate
               pgm.delMember += pgm.Increment;
               pgm.delMember += pgm.Decrement;
               // Calling some member function that will execute the delegate
               pgm.ExecuteSomething();
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // First function to be assigned to the event
           public void Increment(object sender, EventArgs e)
           {
               int res = 32 + 1;
               Console.WriteLine("Inside increment function: " + res.ToString());
           }
           // Second function to be assigned to the event
           public void Decrement(object sender, EventArgs e)
           {
               int res = 32 - 1;
               Console.WriteLine("Inside decrement function: " + res.ToString());
           }
           // Class member event of the delegate type
           public event EventHandler delMember;
           // Function called to execute the delegate member (raise the "event")
           public void ExecuteSomething()
           {
               if (this.delMember != null)
                   this.delMember(this, null);
           }
       }
   }
第二个是 EventHandler<T> 泛型委托,其中 T 是从 EventArgs 派生的类型
public event EventHandler<T> EventName
同样不需要声明自定义委托类型。
这是声明向事件处理程序传递数据的事件的常用方法。
请注意,在这种情况下,您仍然需要声明从 EventArgs 派生的类型 T。
C# 示例

利用 EventHandler<T> 的事件

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace EventLab02
   {
       // the class containing the event data passed to the event handler
       public class IntEventData : EventArgs
       {
           public int IntParm = 0;
           // constructor
           public IntEventData(int i)
           {
               IntParm = i;
           }
       }
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Use += oper to assign functions to the delegate
               pgm.delMember += pgm.Increment;
               pgm.delMember += pgm.Decrement;
               // Calling some member function that will execute the delegate
               pgm.ExecuteSomething();
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // First function to be assigned to a delegate
           public void Increment(object sender, IntEventData e)
           {
               int res = e.IntParm + 1;
               Console.WriteLine("Inside increment function: " + res.ToString());
           }
           // Second function to be assigned to a delegate
           public void Decrement(object sender, IntEventData e)
           {
               int res = e.IntParm - 1;
               Console.WriteLine("Inside decrement function: " + res.ToString());
           }
           // Class member event of the delegate type
           public event EventHandler<IntEventData> delMember;
           // Function called to execute the delegate member (raise the "event")
           public void ExecuteSomething()
           {
               if (this.delMember != null)
                   this.delMember(this, new IntEventData(32));
           }
       }
   }
关于事件和委托的基本示例到此结束。



服务、线程和应用程序域

考试目标:在 .NET Framework 应用程序中实现服务进程、线程和应用程序域

主题

服务

此处“服务”一词指的是 Windows 服务。Windows 服务的基本定义是长时间运行的进程,不需要用户界面。为什么需要一个不需要用户界面的长时间运行的进程?主要有两个原因

  • 执行不需要用户干预的维护任务。例如,备份软件会定期检查备份计划并在需要时执行不同的备份任务。这不需要用户界面。
  • 响应来自其他进程或来自操作系统的请求。例如,IIS(处理 Web 请求的 Windows 组件)这样的 http 服务器将接收来自客户端浏览器的 http 请求,并向这些相同的浏览器生成响应(html 页面)。数据库进程是另一个很好的例子。同样,http 服务器不需要用户界面,因为与客户端的界面由客户端浏览器组件管理。

关于服务的考试目标非常基础,涉及到您在处理服务时遇到的第一个问题

  • 由于服务没有用户界面,那么谁会启动和停止它呢?答案是操作系统直接执行服务,但您必须将您的服务“注册”以让系统知道它在哪里以及如何处理它(这就是安装过程)
  • 具有用户界面的进程本质上是在等待来自用户的事件。在没有消息泵的情况下,服务是如何“工作”的(消息泵是在典型的在线应用程序中获取用户输入的技术)?
  • 如果服务执行用户界面功能,它将“挂起”,等待不存在的用户。您如何避免这种情况?

更重要、更复杂的架构问题不在本考试范围内,将在企业开发考试中讨论。

多线程


Clipboard

待办事项
对 .NET 中的多线程支持的描述


应用程序域


Clipboard

待办事项
CLI 中应用程序域的简短描述


类、接口和工具

实现、安装和控制服务

考试目标:实现、安装和控制服务

(参考 System.ServiceProcess 命名空间)

从 ServiceBase 类继承 - MSDN

服务是长时间运行的可执行文件。它不提供用户界面,也不需要任何用户登录到计算机。服务以 System 身份运行,但可以选择以其他用户帐户运行。ServiceBase 类是服务的基类。在创建新服务时,必须从它派生。
几乎所有服务都将覆盖 ServiceBase 的 OnStart 和 OnStop 方法。

ServiceController 类和 ServiceControllerPermission 类

ServiceController 类 - MSDN
ServiceControllerPermission 类 - MSDN

ServiceInstaller 和 ServiceProcessInstaller 类

ServiceInstaller - MSDN
ServiceProcessInstaller 类 - MSDN

ServiceChangeDescription 结构和 ServiceChangeReason 枚举

SessionChangeDescription 结构 - MSDN
SessionChangeReason 枚举 - MSDN

开发多线程应用程序

考试目标:开发多线程 .NET Framework 应用程序

(参考 System.Threading 命名空间)

Thread 类 - MSDN

ThreadPool 类 - MSDN

ThreadStart 委托、ParameterizedThreadStart 委托和 SynchronizationContext 类

ThreadStart 委托 - MSDN
创建线程最简单的方法是实例化 Thread 类。Thread 构造函数接受一个委托参数。ThreadStart 委托指向包含你的逻辑的方法。例如
Thread t1 = new Thread (new ThreadStart(LengthyLogic));
public void LengthyLogic ()
{
  // Logic code
}
ParameterizedThreadStart 委托 - MSDN
当你启动线程时,有时你需要传递一些数据进行处理。.NET 2.0 提供了一个新的委托 ParameterizedThreadStart,它接受类型为 object 的参数。该类有一个新的重载函数 Thread.Start。它允许你指定要传递到线程的值。这种方法很简单,但不是类型安全的。例如
Thread t1 = new Thread(new ParameterizedThreadStart(LengthyLogic));
// Use the overload of the Start method that has a parameter of type Object.
t1.Start(myData);
static void LengthyLogic(object data)
{
  // Logic code
}
SynchronizationContext 类 - MSDN
SynchronizationContext 类的 Code Project 示例 [1]

Timeout 类、Timer 类、TimerCallback 委托、WaitCallback 委托、WaitHandle 类和 WaitOrTimerCallback 委托

Timeout 类 - MSDN
Timer 类 - MSDN
TimerCallback 委托 - MSDN
WaitCallback 委托 - MSDN
WaitHandle 类 - MSDN
WaitOrTimerCallback 委托 - MSDN

ThreadExceptionEventArgs 类和 ThreadExceptionEventHanlder 类

ThreadExceptionEventArgs 类 - MSDN
ThreadExceptionEventHandler 类 - MSDN

ThreadState 枚举和 ThreadPriority 枚举

ThreadState 枚举 - MSDN
ThreadPriority 枚举 - MSDN

ReaderWriterLock 类 - MSDN

AutoResetEvent 类和 ManualResetEvent 类

AutoResetEvent 类 - MSDN
ManualResetEvent 类 - MSDN

IAsyncResult 接口和 ICancelableAsyncResult 接口

(参考 System 命名空间)
IAsyncResult 接口 - MSDN
ICancelableAsyncResult 接口 - MSDN

EventWaitHandle 类、RegisterWaitHandle 类、SendOrPostCallback 委托和 IOCompletionCallback 委托

EventWaitHandle 类 - MSDN
RegisterWaitHandle 类 - MSDN
这是考试目标列表和培训套件中的一个错字。术语RegisterWaitForSingleObject应该改为搜索(参见 KB
SendOrPostCallback 委托 - MSDN
IOCompletionCallback 委托 - MSDN

Interlocked 类、NativeOverlapped 结构和 Overlapped 类

Interlocked 类 - MSDN
NativeOverlapped 结构 - MSDN
Overlapped 类 - MSDN

ExecutionContext 类、HostExecutionContext 类、HostExecutionContext 管理器和 ContextCallback 委托

ExecutionContext 类 - MSDN
HostExecutionContext 类 - MSDN
HostExecutionContext 管理器 - MSDN
实际上这里指的是 HostExecutionContextManager 类
ContextCallback 委托 - MSDN

LockCookie 结构、Monitor 类、Mutex 类和 Semaphore 类 MSDN]

LockCookie 结构 - MSDN
Monitor 类 - MSDN
Mutex 类 - MSDN
Semaphore 类 - MSDN
Lock 与 Monitor 与 Mutex - MSDN

使用应用程序域

考试目标:通过使用应用程序域,在 .NET Framework 应用程序中为公共语言运行时创建隔离单元

(参考 System 命名空间)

创建应用程序域

参见 MSDN

应用程序域是将进程划分为多个部分。在不同应用程序域中运行的应用程序与它们在不同进程中运行一样隔离。因此它们无法访问另一个应用程序域中的内存。但是,如果运行的是本机代码,它可以获得对整个进程的无限访问权限,包括其他应用程序域。

应用程序域更容易维护,速度也更快,因为在应用程序域之间进行通信比在进程之间进行通信更容易。应用程序域可以保存多个程序集。

要创建应用程序域,你必须至少提供新应用程序域的名称

 AppDomain ad = AppDomain.CreateDomain("Name");

使用AppDomain.CurrentDomain获取调用线程正在使用的应用程序域。

将程序集加载到应用程序域中

参见 MSDN

可以使用AppDomain.ExecuteAssemblyByName按名称执行程序集

 AppDomain ad = AppDomain.CreateDomain("Name");
 ad.ExecuteAssemblyByName("aname, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a9b8c7d6");

或者使用AppDomain.ExecuteAssembly提供程序集的路径

 AppDomain ad = AppDomain.CreateDomain("Name");
 ad.ExecuteAssembly(@"c:\path\to\file.exe");
卸载应用程序域

参见 MSDN

无法从默认应用程序域中卸载程序集。但是,如果程序集加载到不同的应用程序域中,你可以卸载整个应用程序域,包括该应用程序域中的所有程序集。

要卸载应用程序域,请使用静态AppDomain.Unload函数

 AppDomain ad = AppDomain.CreateDomain("Name");
 AppDomain.Unload(ad);  
配置应用程序域

参见 MSDN

修改应用程序域配置的最可能原因是限制某些权限,以在攻击者利用程序集中的漏洞时限制损害。

一个示例是在Internet Zone中运行程序集。Internet Zone 具有有限的权限。为此,创建一个Zone Evidence,并在创建应用程序域时将其作为参数提供

 object [] myEvidenceTypes = {new Zone (SecurityZone.Internet)};
 Evidence myEvidence = new  Evidence(myEvidenceTypes, null);
 AppDomain ad = AppDomain.CreateDomain("Name", myEvidence); // Pass the Evidence when creating the App. Domain
 ad.ExecuteAssembly(@"c:\path\to\file.exe");
 

也可以在具有不同权限的应用程序域中只执行一个程序集;

 object [] myEvidenceTypes = {new Zone (SecurityZone.Internet)};
 Evidence myEvidence = new  Evidence(myEvidenceTypes, null);
 AppDomain ad = AppDomain.CreateDomain("Name");
 ad.ExecuteAssembly(@"c:\path\to\file.exe", myEvidence); // Pass the Evidence in the ExecuteAssembly function

除了 Evidence,你还可以使用 AppDomainSetup 类来设置其他属性。

 AppDomainSetup ads = new AppDomainSetup();
 ads.ApplicationBase = @"c:\Test";
 ads.DisallowCodeDownload = true;
 AppDomain ad = AppDomain.CreateDomain("Name", null, ads); // use null as second parameter for default Evidence
从应用程序域中检索设置信息

参见 MSDN

使用 AppDomain 的SetupInformation属性从该应用程序域中读取设置;

 AppDomainSetup ads = AppDomain.CurrentDomain.SetupInformation;
 Console.WriteLine(ads.ConfigurationFile);
 Console.WriteLine(ads.ApplicationName);


配置、诊断、管理和安装

考试目标:将配置、诊断、管理和安装功能嵌入到 .NET Framework 应用程序中

主题

配置管理

这里以限制的方式使用配置管理。它指的是将应用程序适应到特定的执行环境或用户。这通常通过使用配置文件来完成,这些配置文件指定应用程序的运行时参数。此类配置信息的典型示例是用于定位和连接到应用程序数据库的连接字符串。考试目标都与应用程序与其配置文件的实际交互有关。

配置文件本身的管理或设计是一个庞大的主题,考试目标没有涉及,因此在本学习指南中不会介绍。

.NET Framework 安装程序

事件日志

MSDN 的定义是“Windows 事件日志允许你的应用程序和组件记录有关重要事件的信息。你可以使用这些记录来审核对系统的访问、排查问题以及重新创建使用模式”。

有关一般讨论,请参见 MSDN

有关 EventLog 类的详细信息以及使用它的注意事项,请参见 MSDN

性能监控

调试和跟踪

管理信息和事件

Windows Management Instrumentation - MSDN



Clipboard

待办事项
描述 WMI 在 .NET 中的集成


类、接口和工具

嵌入配置管理

考试目标:将配置管理功能嵌入到 .NET Framework 应用程序中。

(参考 System.Configuration 命名空间)

Configuration 类和 ConfigurationManager 类

Configuration 类 - MSDN
ConfigurationManager 类 - MSDN

ConfigurationSettings 类、ConfigurationElement 类、ConfigurationElementCollection 类和 ConfigurationElementProperty 类

ConfigurationSettings 类 - MSDN
ConfigurationElement 类 - MSDN
ConfigurationElementCollection 类 - MSDN
ConfigurationElementProperty 类 - MSDN

实现 IConfigurationSectionHandler 接口 - MSDN

ConfigurationSection 类、ConfigurationSectionCollection 类、ConfigurationSectionGroup 类和 ConfigurationSectionGroupCollection 类

ConfigurationSection 类 - MSDN
ConfigurationSectionCollection 类 - MSDN
ConfigurationSectionGroup 类 - MSDN
ConfigurationSectionGroupCollection - MSDN

实现 ISettingsProviderService 接口 - MSDN

实现 IApplicationSettingsProvider 接口 - MSDN

ConfigurationValidationBase 类 - MSDN

在 MSDN 上没有直接的结果 - 待检查

实现 IConfigurationSystem 接口 - MSDN

创建自定义安装程序并配置应用程序

考试目标:使用 System.Configuration.Install 命名空间创建自定义 Microsoft Windows 安装程序以用于 .NET Framework 组件,并使用配置文件、环境变量和 .NET Framework 配置工具 (Mscorcfg.msc) 配置 .NET Framework 应用程序。

有关本节中讨论的程序的“食谱”,请参阅 MSDN 和相应的 How-To 部分。

Installer 类 - MSDN

配置 .NET Framework 应用程序应使用的运行时版本 - MSDN

配置运行时应在何处搜索程序集 - MSDN

配置程序集的位置以及要使用的程序集版本 - MSDNMSDN

指示运行时在搜索程序集时使用 DEVPATH 环境变量 - MSDN

AssemblyInstaller 类 - MSDN

ComponentInstaller 类 - MSDN

使用 .NET Framework 配置工具 (Mscorcfg.msc) 配置 .NET Framework 应用程序 - MSDN

ManagedInstaller 类 - MSDN

InstallContext 类 - MSDN

InstallerCollection 类 - MSDN

实现 IManagedInstaller 接口 - MSDN

InstallEventHandler 委托 - MSDN

配置并发垃圾回收 - MSDN

使用配置文件注册远程对象 - MSDN

管理事件日志

考试目标:使用 System.Diagnostics 命名空间管理事件日志

EventLog 类 - MSDN

EventSourceCreationData 类 - MSDN

写入事件日志

MSDN

从事件日志读取

MSDN

创建新的事件日志

通过创建第一个写入该日志的事件源来创建 EventLog。

执行此操作的两种最简单方法是

  • 使用 EventLog.CreateEventSource 方法
  • 创建 EventLog 实例,指定源,然后写入日志。实际创建发生在执行第一个写入时。

请注意,即使在注册表中创建了表示源的对象,System.Diagnostics 命名空间中也没有“EventSource”类。

C# EventLog 创建示例

   using System;
   using System.Collections.Generic;
   using System.Text;
   using System.Diagnostics;
   namespace EventLogLab1
   {
       class Program
       {
           static void Main(string[] args)
           {
               try
               {
                   EventLog log1 = new EventLog("EvtLab2Log");
                   log1.Source = "EvtLab2Source";
                   // Actual creation happens next
                   log1.WriteEntry("Example message", EventLogEntryType.Information,
                       123, 1);
               }
               catch (Exception e)
               {
                   Console.WriteLine(e.Message);
               }
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
   }

建议的方法是使用 EventLogInstaller 类在应用程序安装期间进行安装,但这似乎没有在培训工具包中介绍(因此可能不在考试中)。有关参考目的,请参阅 MSDN

管理进程并监控性能

考试目标:使用 .NET Framework 2.0 的诊断功能管理系统进程并监控 .NET Framework 应用程序的性能。

(参考 System.Diagnostics 命名空间)

获取所有正在运行的进程的列表。

Process 类 - MSDN
有关 GetCurrentProcess()、GetProcessesByName()、GetProcesses() 和 GetProcessById() 的示例代码,请参阅 MSDN

检索有关当前进程的信息 - MSDN

获取进程加载的所有模块的列表

Process.Modules 属性返回一个强类型集合,其中包含代表 Process 当前加载的模块的 ProcessModule 对象。
有关 Process.Modules 属性,请参阅 MSDN
有关 ProcessModule 类,请参阅 MSDN

PerformanceCounter 类、PerformanceCounterCategory 类和 CounterCreationData 类

PerformanceCounter 类 - MSDN
PerformanceCounterCategory - MSDN
CounterCreationData 类 - MSDN

使用和不使用命令行参数启动进程

启动进程概述
使用一个或多个重载的 Process.Start() 方法启动进程。当将敏感数据(如密码)传递给 Process.Start() 时,请使用接受 SecureString 作为参数类型的两个重载 Start() 方法之一。
外部链接

StackTrace 类 - MSDN

StackFrame 类 - MSDN

调试和跟踪

考试目标:使用 System.Diagnostics 命名空间调试和跟踪 .NET Framework 应用程序。

Debug 类和 Debugger 类

Debug 类 - MSDN
Debug 类的四个静态写入方法 - WriteWriteLineWriteIfWriteLineIf - 允许您将调试消息写入以检查程序流程并捕获错误。在程序的发布版本中,对这些方法的调用将被忽略(有关详细信息,请参阅下面的“ConditionalAttribute 属性”)。
在 Visual Studio 中,Debug 的写入方法的默认目标是输出窗口。您可以使用 Debug 的 Listeners 属性访问关联的 TraceListenerCollection。以下代码显示了如何使用集合的 Remove 和 Add 方法来控制调试消息发送到的位置。如果您添加了多个相同类型的侦听器,则关联的目标会多次接收文本。
C# 代码示例

Debug 类示例

   using System;
   using System.Diagnostics;
   using System.IO;
   using System.Reflection;
   class Program
   {
       static void Main(string[] args)
       {
           Debug.WriteLine("This is (by default) printed in the Output window.");
           //remove default listener
           Debug.Listeners.RemoveAt(0);
           //add a listener that can write to the Console window
           Debug.Listeners.Add(new ConsoleTraceListener());
           Debug.WriteLine("This is printed in the console window.");
           //add a default listener again
           Debug.Listeners.Add(new DefaultTraceListener());
           Debug.WriteLine("This is printed in both the Output and the Console window.");
           //remove all listeners
           Debug.Listeners.Clear();
           //add a listener that writes to a file
           Debug.Listeners.Add(new TextWriterTraceListener(File.Create("C:\\test.txt")));
           Debug.WriteLine("This is only printed to the newly created file.");
           //here we need to flush the output buffer
           Debug.Flush();
           //keep console window open in debug mode
           Console.ReadLine();
       }
   }
Debugger 类 - MSDN

Trace 类 - MSDN

CorrelationManager 类 - MSDN

TraceListener 类 - MSDN

TraceSource 类 - MSDN

TraceSwitch 类 - MSDN

XmlWriterTraceListener 类 - MSDN

DelimitedListTraceListener 类 - MSDN

EventlogTraceListener 类 - MSDN

调试器属性 - MSDN

DebuggerBrowsableAttribute 类 - MSDN
DebuggerDisplayAttribute 类 - MSDN
DebuggerHiddenAttribute 类 - MSDN
DebuggerNonUserCodeAttribute 类 - MSDN
DebuggerStepperBoundaryAttribute 类 - MSDN
DebuggerStepThroughAttribute 类 - MSDN
DebuggerTypeProxyAttribute 类 - MSDN
DebuggerVisualizerAttribute 类 - MSDN

嵌入管理信息

考试目标: 将管理信息和事件嵌入到 .NET Framework 应用程序中。

(参考 System.Management 命名空间 - MSDN)

使用 ManagementObjectSearcher 类及其派生类检索管理对象集合

System.Management 命名空间中的类允许您使用 Windows 管理规范 (WMI) 来管理计算机系统。此命名空间中最值得注意的类是 ManagementObjectSearcher,您可以使用它根据 WMI 查询检索管理对象。
可以通过这种方式检索的信息范围从计算机机箱类型(笔记本电脑、台式机……)到处理器和硬盘详细信息,再到有关正在运行的服务和进程的信息。请参阅 MSDN 上的 WMI 参考,了解所有 WMI 类及其属性的概述。
在下面的示例中,定义了一个粗略且不干净的方法,用于打印给定 WMI 类中所有管理信息对象的属性。然后使用它来打印有关当前计算机系统的信息。
C# 示例代码

WMI 基本示例

   class Program
   {
       static void Main(string[] args)
       {
           //Print all management info in the WMI class Win32_ComputerSystem
           PrintManagementInfo("Win32_ComputerSystem");
           //wait for user input to keep console window up in debug mode
           Console.ReadLine();
       }
       static void PrintManagementInfo(string WMIClassName)
       {
           ManagementObjectSearcher mos;
           //Get all managementobjects of the specified class
           mos = new ManagementObjectSearcher("SELECT * FROM " + WMIClassName);
           foreach (ManagementObject MOCollection in mos.Get())
           {
               foreach (PropertyData p in MOCollection.Properties)
               {
                   //Some properties are arrays,
                   //in which case the code below only prints
                   //the type of the array.
                   //Add a check with IsArray() and a loop
                   //to display the individual array values.
                   Console.WriteLine("{0}: {1}", p.Name, p.Value);
               }
           }
       }
   }
ManagementObjectSearcher 类 - MSDN
枚举计算机上的所有磁盘驱动器、网络适配器和进程
以下代码使用 ManagementObjectSearcher 构造函数 的一个重载来列出系统上的所有物理和逻辑磁盘驱动器、网络适配器和正在运行的进程。
C# 示例代码

ManagementObjectSearcher 示例

   class Program
   {
       static void Main(string[] args)
       {
           //
           Console.WriteLine("Physical disks: ");
           PrintManagementInfoProperty("Win32_DiskDrive", "Name");
           Console.WriteLine("*****************************");
           //
           Console.WriteLine("Logical disks: ");
           PrintManagementInfoProperty("Win32_LogicalDisk", "Name");
           Console.WriteLine("*****************************");
           //
           Console.WriteLine("Network adapters: ");
           PrintManagementInfoProperty("Win32_NetworkAdapter", "Name");
           Console.WriteLine("*****************************");
           //  
           Console.WriteLine("Processes: ");
           PrintManagementInfoProperty("Win32_Process", "Name");
           Console.WriteLine("*****************************");
           //  
           //wait for user input to keep console window up in debug mode
           Console.ReadLine();
       }
       static void PrintManagementInfoProperty(string WMIClassName, string WMIPropertyName)
       {
           ManagementObjectSearcher mos;
           //Get the specified property for all objects of the specified class
           mos = new ManagementObjectSearcher("SELECT " + WMIPropertyName + " FROM " + WMIClassName);
           foreach (ManagementObject mo in mos.Get())
           {
               PropertyData p = mo.Properties[WMIPropertyName]; 
               Console.WriteLine("{0}", p.Value);
           }
       }
   }
检索有关所有网络连接的信息
提供示例
检索有关所有暂停的服务的信息
以下代码使用 ManagementObjectSearcher 对象检索所有正在运行的服务,然后显示其名称。
C# 示例代码

枚举服务示例

   class Program
   {
       static void Main(string[] args)
       {
           Console.WriteLine("Running services: ");
           //form the query, providing a WMI class name and a condition
           SelectQuery query = new SelectQuery("Win32_Service", "State='Running'");
           //find matching management objects
           ManagementObjectSearcher mos = new ManagementObjectSearcher(query);
           //
           foreach (ManagementObject mo in mos.Get())
           {
               Console.WriteLine(mo.Properties["Name"].Value);
           }
           //                        
           //wait for user input to keep console window up in debug mode
           Console.ReadLine();
       }
   }

ManagementQuery 类 - MSDN

EventQuery 类 - MSDN

ObjectQuery 类 - MSDN

使用 ManagementEventWatcher 类订阅管理事件 - MSDN


序列化和输入/输出

考试目标: 在 .NET Framework 应用程序中实现序列化和输入/输出功能

主题

序列化

维基百科对序列化的定义是:“在数据存储和传输的上下文中,序列化是将对象保存到存储介质(例如文件或内存缓冲区)或通过网络连接链路以二进制形式传输的过程”。

这里解决的问题是,对象是由正在运行的进程创建的,因此与该进程实例的生命周期绑定在一起。如果由于任何原因,而且有很多原因,您想要在另一个进程实例的上下文中“传输”该对象,那么您就会遇到问题,您可以通过在原始进程中“保存”对象的 state 并在目标进程中“恢复”它来解决。这个“保存”部分称为序列化,而“恢复”部分称为反序列化。

可序列化属性

如果对象的类名前缀为 [Serializable] 属性,则该对象是可序列化的。

对象序列化

可以使用 BinaryFormatter 类 来序列化对象。要序列化,请使用 BinaryFormatter 的 Serialize() 方法,该方法将流和可序列化对象作为参数。要反序列化,请使用 BinaryFormatter 的 Deserialize() 方法,该方法将流作为参数并返回一个可以强制转换为原始对象类型的对象。请记住,在使用完流后通过调用流的 Close() 方法来关闭流。

XML 序列化

可以使用 XmlSerializer 类 来序列化对象。要序列化,请使用 XmlSerializer 的 Serialize() 方法,该方法将流和可序列化对象作为参数。要反序列化,请使用 XmlSerializer 的 Deserialize() 方法,该方法将流作为参数并返回一个可以强制转换为原始对象类型的对象。请记住,在使用完流后通过调用流的 Close() 方法来关闭流。

有关 XML 和 SOAP 序列化的概述,请参阅 MSDN

自定义序列化

ISerializable 接口允许对象控制自己的序列化和反序列化。

阅读器

编写器

格式化程序

格式化程序用于将对象序列化到流中。

文件 I/O

管理字节流

压缩

隔离存储

有关隔离存储任务的一般讨论,请参阅 MSDN

类、接口和工具

序列化和反序列化

考试目标: 使用运行时序列化技术序列化或反序列化对象或对象图。

(参考 System.Runtime.Serialization 命名空间)

序列化接口

IDeserializationCallback 接口 - MSDN

IFormatter 接口和 IFormatterConverter 接口

IFormatter 接口 - MSDN
IFormatterConverter 接口 - MSDN

ISerializable 接口 - MSDN

序列化属性
有关一些序列化属性示例,请参阅 MSDN

OnDeserializedAttribute 类和 OnDeserializingAttribute 类

OnDeserializedAttribute 类 - MSDN
OnDeserializingAttribute 类 - MSDN

OnSerializedAttribute 类和 OnSerializingAttribute 类

OnSerializedAttribute 类 - MSDN
OnSerializingAttribute 类 - MSDN

OptionalFieldAttribute 类 - MSDN

SerializationEntry 结构和 SerializationInfo 类

SerializationEntry 结构 - MSDN

SerializationInfo 类 - MSDN

ObjectManager 类

ObjectManager 类 - MSDN

Formatter 类、FormatterConverter 类和 FormatterServices 类

Formatter 类 - MSDN

FormatterConverter 类 - MSDN

FormatterServices 类 - MSDN

StreamingContext 结构

StreamingContext 结构 - MSDN

XML 序列化

考试目标: 使用 System.Xml.Serialization 命名空间控制对象以 XML 格式序列化。

XmlSerializer 类 - MSDN

考试目标:使用 XmlSerializer 类将对象序列化和反序列化为 XML 格式

使用序列化属性控制序列化 - MSDN

有关控制序列化的属性列表,请参阅 MSDN

实现 XML 序列化接口以提供 XML 序列化的自定义格式 - MSDN

System.Xml.Serialization 命名空间提供委托和事件处理程序 - MSDN

自定义序列化

考试目标: 使用序列化格式化程序类实现自定义序列化格式。

SoapFormatter 类 - MSDN

(参考 System.Runtime.Serialization.Formatters.Soap 命名空间)

BinaryFormatter 类 - MSDN

(参考 System.Runtime.Serialization.Formatters.Binary 命名空间

文件系统类

考试目标: 使用文件系统类访问文件和文件夹。

(参考 System.IO 命名空间)

File 类和 FileInfo 类

有关常见的 I/O 任务,请参阅 MSDN
文件类 - MSDN
FileInfo 类 - MSDN

目录类和 DirectoryInfo 类

目录类 - MSDN
DirectoryInfo 类 - MSDN

DriveInfo 类和 DriveType 枚举

DriveInfo 类 - MSDN
DriveType 枚举 - MSDN

FileSystemInfo 类和 FileSystemWatcher 类

FileSystemInfo 类
FileSystemWatcher 类
FileSystemWatcher 类旨在检测文件系统中的更改。
它可以使用 Filter 和 Path 属性进行参数化。
  Example: 
  FileSystemWatcher w = new FileSystemWatcher();
  w.Filter = "*.txt";
  w.Path = @"C:\Windows";
Filter 属性仅用于检查文件名的模式。因此,不要在其中使用目录路径。
您可以添加 WaitForChanged(..) 等方法来监视指定区域的更改。

Path 类 - MSDN

System.IO.Path 类具有许多用于创建和解析资源路径的 有用的静态方法

ErrorEventArgs 类和 ErrorEventHandler 委托

ErrorEventArgs 类 - MSDN
ErrorEventHandler 委托 - MSDN

RenamedEventArgs 类和 RenamedEventHandler 委托

RenamedEventArgs 类 - MSDN
RenamedEventHandler 委托 - MSDN

字节流

考试目标:使用 Stream 类管理字节流。

(参考 System.IO 命名空间)

FileStream 类 - MSDN

Stream 类 - MSDN

System.IO.Stream 是所有其他流继承的抽象基类。无法实例化 Stream 类。而是使用从 Stream 派生的其他类之一。
就 70-536 考试目标而言,从 Stream 继承的最重要的类是
  • System.IO.FileStream
  • System.IO.MemoryStream
  • System.IO.Compression.DeflateStream
  • System.IO.Compression.GZipStream
  • System.Security.Cryptography.CryptoStream
  • System.IO.BufferedStream
有关从 Stream 继承的类的完整列表,请参见 MSDN
有关文件和流 I/O 的讨论,请参见 MSDN

MemoryStream 类 - MSDN

BufferedStream 类 - MSDN

Reader 和 Writer 类

考试目标:使用 Reader 和 Writer 类管理 .NET Framework 应用程序数据。

(参考 System.IO 命名空间)

StringReader 类和 StringWriter 类 - MSDNMSDN

StringReader 和 StringWriter 继承自 TextReader/TextWriter。
  • StringReader 是字符串的 TextReader。
  • StringWriter 是字符串的 TextWriter。

TextReader 类和 TextWriter 类

TextReader 类 - MSDN
TextReader 和 TextWriter 是抽象基类,StreamReader、StreamWriter、StringReader 和 StringWriter 从中派生。StreamReader 和 StringReader 从 TextReader 派生。StreamWriter 和 StringWriter 从 TextWriter 派生。
TextWriter 类 - MSDN

StreamReader 类和 StreamWriter 类 - MSDNMSDN

StreamReader 和 StreamWriter 类提供用于读写基于字符的流的基本功能 (ReadLine()、WriteLine()、ReadToEnd())。
StreamReader 和 StreamWriter 继承自抽象类 TextReader 和 TextWriter
  • StreamReader 是流的 TextReader。
  • StreamWriter 是流的 TextWriter。
StreamReader 的 Peek 和 Read 方法
  • Peek 方法获取特定位置的字符,但不前进。
  • Read 方法获取特定位置的字符并前进。

BinaryReader 类和 BinaryWriter 类

BinaryReader 类 - MSDN
BinaryWriter 类 - MSDN

压缩和隔离存储

考试目标:压缩或解压缩 .NET Framework 应用程序中的流信息,并通过使用隔离存储来提高应用程序数据的安全性。

(参考 System.IO.Compression 命名空间)

(参考 System.IO.IsolatedStorage 命名空间)

IsolatedStorageFile 类 - MSDN

IsolatedStorageFileStream 类 - MSDN

DeflateStream 类 - MSDN

GZipStream 类 - MSDN


安全

考试目标:通过使用 .NET Framework 2.0 安全功能来提高 .NET Framework 应用程序的安全性

主题

代码访问安全

代码访问安全 (CAS) 允许控制授予特定托管应用程序的各种权限。 MSDN

权限允许访问系统资源。权限集是权限的集合。代码组将一个权限集与一个证据类型关联起来。证据用于识别程序集。证据类型可以包括应用程序目录、程序集的加密哈希、发布者的数字签名、下载程序集的站点、程序集的加密强名称、下载程序集的 URL 以及程序集运行的安全区域。安全区域包括计算机区域、本地 Intranet 区域、Internet 区域、受信任站点和不受信任站点。请参阅 Internet Explorer 中的“Internet 选项”安全选项卡,以查看各种安全区域。一个程序集可以与多个代码组关联。权限集可以与多个代码组关联。

安全策略是代码组和权限集的逻辑分组。不受信任的托管程序集必须通过四个安全策略:企业安全策略、机器安全策略、用户安全策略和应用程序域安全策略。这些安全策略中的任何一个都可以拒绝不受信任的托管程序集的权限。

类、接口和工具

实现代码访问安全

考试目标:实现代码访问安全以提高 .NET Framework 应用程序的安全性。

(参考 System.Security 命名空间)

SecurityManager 类 - MSDN

CodeAccessPermission 类 - MSDN

使用代码访问安全策略工具 (Caspol.exe) 在机器、用户和企业策略级别修改代码访问安全策略 - MSDN

PermissionSet 类、NamedPermissionSet 类和 PermissionSetCollection 类

PermissionSet 类 - MSDN
NamedPermissionSet 类 - MSDN
PermissionSetCollection 类
似乎没有这样的东西,需要调查…

标准安全接口

IEvidenceFactory 接口 - MSDN
IPermission 接口 - MSDN

实现访问控制

考试目标:使用 System.Security.AccessControl 类实现访问控制。

DirectorySecurity 类、FileSecurity 类、FileSystemSecurity 类和 RegistrySecurity 类

DirectorySecurity 类 - MSDN
FileSecurity 类 - MSDN
FileSystemSecurity 类 - MSDN
RegistrySecurity 类 - MSDN

AccessRule 类 - MSDN

AuthorizationRule 类和 AuthorizationRuleCollection 类

AuthorizationRule 类 - MSDN
AuthorizationRuleCollection 类 - MSDN

CommonAce 类、CommonAcl 类、CompoundAce 类、GenericAce 类和 GenericAcl 类

CommonAce 类 - MSDN
CommonAcl 类 - MSDN
CompoundAce 类 - MSDN
GenericAce 类 - MSDN
GenericAcl 类 - MSDN

AuditRule 类 - MSDN

MutexSecurity 类、ObjectSecurity 类和 SemaphoreSecurity 类

MutexSecurity 类 - MSDN
ObjectSecurity 类 - MSDN
SemaphoreSecurity 类 - MSDN

实现自定义身份验证方案

考试目标: 使用 System.Security.Authentication 类实现自定义身份验证方案。

(参考 System.Security.Authentication 命名空间 - MSDN)

有关自定义身份验证方案的参考,请参见 MSDN

加密、解密和哈希数据

考试目标: 使用 System.Security.Cryptography 类加密、解密和哈希数据。

(参考 System.Security.Cryptography 命名空间)

DES 类和 DESCryptoServiceProvider 类

DES 类 - MSDN
DESCryptoServiceProvider 类 - MSDN

HashAlgorithm 类 - MSDN

DSA 类和 DSACryptoServiceProvider 类

DSA 类 - MSDN
DSACryptoServiceProvider 类 - MSDN

SHA1 类和 SHA1CryptoServiceProvider 类

SHA1 类 - MSDN
SHA1CryptoServiceProvider 类 - MSDN

TripleDES 和 TripleDESCryptoServiceProvider 类

TripleDES - MSDN
TripleDESCryptoServiceProvider 类 - MSDN

MD5 类和 MD5CryptoServiceProvider 类

MD5 类 - MSDN
MD5CryptoServiceProvider 类 - MSDN

RSA 类和 RSACryptoServiceProvider 类

RSA 类 - MSDN
RSACryptoServiceProvider 类 - MSDN

RandomNumberGenerator 类 - MSDN

CryptoStream 类 - MSDN

CryptoConfig 类 - MSDN

RC2 类和 RC2CryptoServiceProvider 类

RC2 类 - MSDN
RC2CryptoServiceProvider 类 - MSDN

AssymetricAlgorithm 类 MSDN

ProtectedData 类和 ProtectedMemory 类

ProtectedData 类 - MSDN
ProtectedMemory 类 - MSDN

RijndaelManaged 类和 RijndaelManagedTransform 类

RijndaelManaged 类 - MSDN
RijndaelManagedTransform 类 - MSDN

CspParameters 类 - MSDN

CryptoAPITransform 类 - MSDN

基于哈希的消息认证码 (HMAC) - MSDN

HMACMD5 类 - MSDN
HMACRIPEMD160 类 - MSDN
HMACSHA1 类 - MSDN
HMACSHA256 类 - MSDN
HMACSHA384 类 - MSDN
HMACSHA512 类 - MSDN

控制权限

考试目标: 使用 System.Security.Permission 类控制资源的权限。

(参考 System.Security.Permission 命名空间)

SecurityPermission 类 - MSDN

PrincipalPermission 类 - MSDN

FileIOPermission 类 - MSDN

您也可以在程序集级别或类级别设置 FileIoPermisson 属性。 然后注意 SecurityAction 枚举。
  • SecurityAction.RequestRefuse:指定不应授予的操作。
  • SecurityAction.RequestMinumum:请求一组最小权限。 如果未提供,应用程序将无法执行。

StrongNameIdentityPermission 类 - MSDN

UIPermission 类 - MSDN

UrlIdentityPermission 类 - MSDN

PublisherIdentityPermission 类 - MSDN

GacIdentityPermission 类 - MSDN

FileDialogPermission 类 - MSDN

DataProtectionPermission 类 - MSDN

EnvironmentPermission 类 - MSDN

IUnrestrictedPermission 接口 - MSDN

RegistryPermission 类 - MSDN

IsolatedStorageFilePermission 类 - MSDN

KeyContainerPermission 类 - MSDN

ReflectionPermission 类 - MSDN

StorePermission 类 - MSDN

SiteIdentityPermission 类 - MSDN

ZoneIdentityPermission 类 - MSDN

控制代码权限

考试目标: 使用 System.Security.Policy 类控制代码权限。

(参考 System.Security.Policy 命名空间)

ApplicationSecurityInfo 类和 ApplicationSecurityManager 类

ApplicationSecurityInfo 类 - MSDN
ApplicationSecurityManager 类 - MSDN

ApplicationTrust 类和 ApplicationTrustCollection 类

ApplicationTrust 类 - MSDN
ApplicationTrustCollection 类 - MSDN

Evidence 类和 PermissionRequestEvidence 类

Evidence 类 - MSDN
PermissionRequestEvidence 类 - MSDN

CodeGroup 类、FileCodeGroup 类、FirstMatchCodeGroup 类、NetCodeGroup 类和 UnionCodeGroup 类

CodeGroup 类 - MSDN
FileCodeGroup 类 - MSDN
FirstMatchCodeGroup 类 - MSDN
NetCodeGroup 类 - MSDN
UnionCodeGroup 类 - MSDN

条件类

AllMembershipCondition 类 - MSDN
ApplicationDirectory 类和 ApplicationDirectoryMembershipCondition 类
ApplicationDirectory 类 - MSDN
ApplicationDirectoryMembershipCondition 类 - MSDN
GacMembership 类和 GacMembershipCondition 类
GacMembership 类
在 MSDN 上没有搜索结果!?这里需要进行一些调查。
GacMembershipCondition 类 - MSDN
Hash 类和 HashMembershipCondition 类
Hash 类 - MSDN
HashMembershipCondition 类 - MSDN
Publisher 类和 PublisherMembershipCondition 类
Publisher 类 - MSDN
PublisherMembershipCondition 类 - MSDN
Site 类和 SiteMembershipCondition 类
Site 类 - MSDN
SiteMembershipCondition 类 - MSDN
StrongName 类和 StrongNameMembershipCondition 类
StrongName 类 - MSDN
StrongNameMembershipCondition 类 - MSDN
Url 类和 UrlMembershipConditon 类
Url 类 - MSDN
UrlMembershipConditon 类 - MSDN
Zone 类和 ZoneMembershipCondition 类
Zone 类 - MSDN
ZoneMembershipCondition 类 - MSDN

PolicyLevel 类和 PolicyStatement 类

PolicyLevel 类 - MSDN
PolicyStatement 类 - MSDN

IApplicationTrustManager 接口、IMembershipCondition 接口和 IIdentityPermissionFactory 接口

IApplicationTrustManager 接口 - MSDN
IMembershipCondition 接口 - MSDN
IIdentityPermissionFactory 接口 - MSDN

访问和修改身份信息

考试目标:使用 System.Security.Principal 类访问和修改身份信息。

(参考 System.Security.Principal 命名空间)

GenericIdentity 类和 GenericPrincipal 类

GenericIdentity 类 - MSDN
GenericPrincipal 类 - MSDN

WindowsIdentity 类和 WindowsPrincipal 类

WindowsIdentity 类 - MSDN
WindowsPrincipal 类 - MSDN

NTAccount 类和 SecurityIdentifier 类

NTAccount 类 - MSDN
SecurityIdentifier 类 - MSDN

IIdentity 接口和 IPrincipal 接口

IIdentity 接口 - MSDN
IPrincipal 接口 - MSDN

WindowsImpersonationContext 类 - MSDN

IdentityReference 类和 IdentityReferenceCollection 类

IdentityReference 类 - MSDN
IdentityReferenceCollection 类 - MSDN


互操作性、反射和邮件

考试目标:在 .NET Framework 应用程序中实现互操作性、反射和邮件功能


主题

互操作性

反射

维基百科对计算机科学中反射的定义是:“一个计算机程序可以观察和修改自身结构和行为的过程。由反射驱动的编程范式称为反射式编程”。

该概念在 .NET 中得到广泛实现,因为许多信息都在公共中间语言 (CIL) 级别维护。

除其他事项外,您可以

  • 获取有关正在运行的程序集或程序集组成部分(模块、类、方法等)的信息,这对动态加载的程序集(在编译时未知)特别有用。
  • 在代码中放置自定义属性并在运行时检索这些属性。
  • 动态调用方法。
  • 在运行时“发出”和执行 CIL 代码。

邮件

类、接口和工具

COM 互操作性

考试目标:将 COM 组件公开给 .NET Framework 以及将 .NET Framework 组件公开给 COM

(参考 System.Runtime.InteropServices 命名空间)

将 COM 组件公开给 .NET Framework

首先,.NET 开发人员指南中有一篇文章涵盖了本节的第一部分,请参阅 MSDN

将类型库导入为程序集 - MSDN

添加对类型库的引用
与上述链接相同,请参阅第二段。
类型库导入器 (Tlbimp.exe) - MSDN
从类型库生成互操作程序集 - MSDN
导入的库转换 - MSDN
导入的模块转换 - MSDN
导入的类型转换 - MSDN
导入的成员转换 - MSDN
导入的参数转换 - MSDN
TypeConverter 类 - MSDN

在托管代码中创建 COM 类型 - MSDN

编译互操作项目 - MSDN

部署互操作应用程序 - MSDN

将 .NET Framework 组件公开给 COM

该部分的其余目标直接参考 MSDN

为互操作性限定 .NET Framework 类型 - MSDN

应用互操作属性,例如 ComVisibleAttribute 类 - MSDN

为 COM 打包程序集 - MSDN

部署供 COM 访问的应用程序 - MSDN

调用 DLL 函数

考试目标:在 .NET Framework 应用程序中调用非托管 DLL 函数,并在 .NET Framework 应用程序中控制数据的封送处理。

(参考 System.Runtime.InteropServices 命名空间)

平台调用 - MSDN

创建类以保存 DLL 函数 - MSDN

在托管代码中创建原型 - MSDN

DllImportAttribute 类 - MSND

调用 DLL 函数 - MSDN

在互联网上查找“P/Invoke”。
一个很好的参考资料是:http://www.pinvoke.net/
一些简短的提示
  • 对于 DllAttribute 的“CharSet”,从逻辑上讲,CharSet.Auto 是最安全的方式,因为它会自动检测使用的字符集。字符集可以是 ANSI(Win95/Win98/ME) 或 Unicode (Win2000/WinXP)
  • GetEntryPoint:可能有一个棘手的问题,因为 EntryPoint 指示应调用的方法。如果使用的方法名称与要调用的方法名称不同,则需要此标志。

在特殊情况下调用 DLL 函数,例如传递结构和实现回调函数

与上述链接相同
传递结构 - MSDN
实现回调函数 - MSDN

创建新的 Exception 类并将其映射到 HRESULT - MSDN

默认封送处理行为 - MSDN

使用平台调用封送处理数据 - MSDN

使用 COM 互操作性封送处理数据 - MSDN

MarshalAsAttribute 类和 Marshal 类

MarshalAsAttribute 类 - MSDN
Marshal 类 - MSDN

实现反射

考试目标:在 .NET Framework 应用程序中实现反射功能(参考 System.Reflection 命名空间),并使用 System.Reflection.Emit 命名空间创建元数据、Microsoft 中间语言 (MSIL) 和 PE 文件。

构建自定义属性(附录)

Assembly 类 -MSDN

程序集属性 - MSDN

AssemblyAlgorithmIdAttribute 类 - MSDN
AssemblyCompanyAttribute 类 - MSDN
AssemblyConfigurationAttribute 类 - MSDN
AssemblyCopyrightAttribute 类 - MSDN
AssemblyCultureAttribute 类 - MSDN
AssemblyDefaultAliasAttribute 类 - MSDN
AssemblyDelaySignAttribute 类 - MSDN
AssemblyDescriptionAttribute 类 - MSDN
AssemblyFileVersionAttribute 类 - MSDN
AssemblyFlagsAttribute 类 - MSDN
AssemblyInformationalVersionAttribute 类 - MSDN
AssemblyKeyFileAttribute 类 - MSDN
AssemblyTitleAttribute 类 - MSDN
AssemblyTrademarkAttribute 类 - MSDN
AssemblyVersionAttribute 类 - MSDN

信息类

ConstructorInfo 类 - MSDN
MethodInfo 类 - MSDN
MemberInfo 类 - MSDN
PropertyInfo 类 - MSDN
FieldInfo 类 - MSDN
EventInfo 类 - MSDN
LocalVariableInfo 类 - MSDN

Binder 类和 BindingFlags - MSDN

MethodBase 类和 MethodBody 类

MethodBase 类 - MSDN
MethodBody 类 - MSDN

生成器类

AssemblyBuilder 类 - MSDN
ConstructorBuilder 类 - MSDN
EnumBuilder 类 - MSDN
EventBuilder 类 - MSDN
FieldBuilder 类 - MSDN
LocalBuilder 类 - MSDN
MethodBuilder 类 - MSDN
ModuleBuilder 类 - MSDN
ParameterBuilder 类 - MSDN
PropertyBuilder 类 - MSDN
TypeBuilder 类 - MSDN

发送电子邮件

考试目标:从 .NET Framework 应用程序向简单邮件传输协议 (SMTP) 服务器发送电子邮件以进行传送。

(参考 System.Net.Mail 命名空间)

MailMessage 类 - MSDN

MailAddress 类和 MailAddressCollection 类

MailAddress 类 - MSDN
MailAddressCollection 类 - MSDN

SmtpClient 类、SmtpPermission 类和 SmtpPermissionAttribute 类

SmtpClient 类 - MSDN
SmtpPermission 类 - MSDN
SmtpPermissionAttribute 类 - MSDN

Attachment 类、AttachmentBase 类和 AttachmentCollection 类

Attachment 类 - MSDN
AttachmentBase 类 - MSDN
AttachmentCollection 类 - MSDN

SmtpException 类、SmtpFailedReceipientException 类和 SmtpFailedReceipientsException 类

SmtpException 类 - MSDN
SmtpFailedReceipientException 类 - MSDN
请注意,考试目标页面中存在一个拼写错误,他们使用的是 SmtpFailedReceipientException,而不是 SmtpFailedRecipientException。
SmtpFailedRecipientsException 类 - MSDN
与上面相同的拼写错误

SendCompleteEventHandler 委托 - MSDN

LinkedResource 类和 LinkedResourceCollection 类

LinkedResource 类 - MSDN
LinkedResourceCollection 类 - MSDN

AlternateView 类和 AlternateViewCollection 类

AlternateView 类 - MSDN
AlternateViewCollection 类 - MSDN



全球化、绘图和文本操作

考试目标:在 .NET Framework 应用程序中实现全球化、绘图和文本操作功能。

主题

全球化

绘图

文本操作

在考试目标的上下文中,文本操作涵盖了 3 个主要主题:字符串构建、正则表达式和文本编码。我们在以下段落中分别介绍它们。

String 和 StringBuilder 类

文本操作从字符串表示开始,字符串表示通过 String 类 完成。没有特定的考试目标提及 String 类,但我们添加了一个部分,因为您必须了解它的一些特定特性。

接下来是 StringBuilder 类,它用于有效地构建字符串。

正则表达式

RegexMatchGroup 类共同实现了 .NET 框架中的正则表达式支持。

正则表达式本身就是一个世界,并且已经存在很长时间了。

有一个关于 正则表达式 的维基教科书,它除了其他内容外,还指向这个 教程

.NET 中的正则表达式支持基本上允许

  • 测试字符串与正则表达式模式的匹配(Regex.IsMatch 方法)
  • 提取与模式一部分“匹配”的子字符串(使用 Match 和 Group 类调用 Regex.Match 方法)。
文本编码

类、接口和工具

根据文化信息格式化数据。

(参考 System.Globalization 命名空间)

访问文化和区域信息

考试目标:在 .NET Framework 应用程序中访问文化和区域信息。

CultureInfo 类 - MSDN

CultureTypes 枚举 - MSDN

RegionInfo 类 - MSDN

根据文化格式化日期和时间值。

DateTimeFormatInfo 类 - MSDN

根据文化格式化数字值。

NumberFormatInfo 类 - MSDN

NumberStyles 枚举 - MSDN

执行文化敏感的字符串比较。

CompareInfo 类 - MSDN

CompareOptions 枚举 - MSDN

自定义文化

考试目标:基于现有的文化和区域类构建自定义文化类。

CultureAndRegionInfoBuilder 类 - MSDN

CultureAndRegionModifier 枚举 - MSDN

System.Drawing 命名空间

考试目标:通过使用 System.Drawing 命名空间来增强 .NET Framework 应用程序的用户界面。

画笔、钢笔、颜色和字体

考试目标:通过使用画笔、钢笔、颜色和字体来增强 .NET Framework 应用程序的用户界面。

Brush 类 - MSDN

Brushes 类 - MSDN

SystemBrushes 类 - MSDN

TextureBrush 类 - MSDN

Pen 类 - MSDN

Pens 类 - MSDN

SystemPens 类 - MSDN

SolidBrush 类 - MSDN

Color 结构 - MSDN

ColorConverter 类 - MSDN

ColorTranslator 类 - MSDN

SystemColors 类 - MSDN

StringFormat 类 - MSDN

Font 类 - MSDN

FontConverter 类 - MSDN

FontFamily 类 - MSDN

SystemFonts 类 - MSDN

图形、图像、位图和图标

考试目标:通过使用图形、图像、位图和图标来增强 .NET Framework 应用程序的用户界面

Graphics 类 - MSDN

BufferedGraphics 类 - MSDN

BufferedGraphicsManager 类 - MSDN

Image 类 - MSDN

ImageConverter 类 - MSDN

ImageAnimator 类 - MSDN

Bitmap 类 - MSDN

Icon 类 - MSDN

IconConverter 类 - MSDN

SystemIcons 类 - MSDN

形状和大小

考试目标:通过使用形状和大小来增强 .NET Framework 应用程序的用户界面

Point 结构 - MSDN

PointConverter 类 - MSDN

Rectangle 结构 - MSDN

RectangleConverter 类 - MSDN

Size 结构 - MSDN

SizeConverter 类 - MSDN

Region 类 - MSDN

文本处理和正则表达式

考试目标:增强 .NET Framework 应用程序的文本处理功能,并使用正则表达式在 .NET Framework 应用程序中搜索、修改和控制文本

(参考 System.Text 命名空间)

(参考 System.RegularExpressions 命名空间)

String 类

String 类不是具体的考试目标,但我们在这里讨论了一些它的特性。

String 类 - MSDN

StringBuilder 类

StringBuilder 类用于非常快速的字符串连接。如果您使用传统的字符串连接,它将运行非常慢,因为字符串保存在数组中。每次连接都会导致数组增加其大小,并且内存必须在内部复制到新位置。这非常慢。

为了快速字符串连接,请使用 StringBuilder。它快了大约 1000 倍(取决于您连接的字符串)。

StringBuilder 类 - MSDN


请参考示例以衡量性能差异。

C# 示例

StringBuilder 示例

using System;
using System.Collections;
public class Demo
{
    public static void Main()
    {
        const int len = 30;
        const int loops = 5000;
        //        
        DateTime timeStart, timeStop;
        //         
        // Measure time for normal string concatenation
        timeStart = DateTime.Now;
        string str = "";
        for (int i = 0; i < loops; i++)
        {
            str += new String('x', len);
        }
        timeStop = DateTime.Now;
        int millis = timeStop.Subtract(timeStart).Milliseconds;
        Console.WriteLine("Duration for " + loops + " loops: " + millis + " ms");
        //         
        // Measure time for StringBuilder string concatenation
        StringBuilder sb = new StringBuilder();
        timeStart = DateTime.Now;
        for (int i = 0; i < loops; i++)
        {
            sb.Append(new String('x', len));
        }
        str = sb.ToString();
        timeStop = DateTime.Now;
        millis = timeStop.Subtract(timeStart).Milliseconds;
        Console.WriteLine("Duration for " + loops + " loops: " + millis + " ms");
        //         
        Console.ReadLine();
    }
}
Regex 类

Regex 类 - MSDN

Match 类和 MatchCollection 类

Match 类 - MSDN

MatchCollection 类 - MSDN

Group 类和 GroupCollection 类

Group 类 - MSDN

GroupCollection 类 - MSDN

使用 Encoding 类编码文本

Encoding 类 - MSDN

EncodingInfo 类 - MSDN

ASCIIEncoding 类 - MSDN

UnicodeEncoding 类 - MSDN

UTF8Encoding 类 - MSDN

Encoding 回退类 - MSDN

使用 Decoding 类解码文本。

Decoder 类 - MSDN

Decoder 回退类 - MSDN

Capture 类和 CaptureCollection 类

Capture 类 - MSDN

CaptureCollection 类 - MSDN

另请参阅

参考

70-536 培训资料

.NET Framework 应用程序开发基础自学培训资料,第二版 Tony Northrup Microsoft Press

我们将其称为“培训资料”,因为它被 Microsoft 推荐为 70-536 考试的培训辅助资料(例如,请参见 MCPD)。

如果您使用培训资料,您可能想要查看 这里 以了解已知的更正列表。本维基教科书中列出了其他可能的更正。

C Sharp 2005

Visual C#: The language - 2005 Edition Donis Marchall Microsoft Press

也推荐作为 70-536 考试的培训辅助资料(例如,请参见 MCPD)。

ECMA 335

如果您想更深入地了解通用语言基础设施的定义,可以下载 官方规范

这对于考试来说不是必需的,但我们会使用规范几次来澄清 MSDN 文档或其他参考手册中的歧义。


附录

附录是一系列关于特定主题的文章,它们被正文引用,但没有集成在正文中。


附录:泛型类型

简介

为了理解泛型,我们首先应该看看为什么要使用它们。这最好用一个简单的例子来解释。假设我们想要创建一个客户集合,这是我们经常做的事情。我们使用一个简单的 Client 类,它具有姓名和帐号。通常,ArrayList 用于像这样在内存中存储多个客户

  /// <summary>
  /// Representation of a client
  /// </summary>
  public class Client
  {
      private string _name;
      private string _accountNumber;
       
      /// <summary>
      /// Gets or sets the account number.
      /// </summary>
      /// <value>The account number.</value>
      public string AccountNumber
      {
          get { return _accountNumber; }
          set { _accountNumber = value; }
      }
       
      /// <summary>
      /// Gets or sets the name.
      /// </summary>
      /// <value>The name.</value>
      public string Name
      {
          get { return _name; }
          set { _name = value; }
      }
       
      /// <summary>
      /// Initializes a new instance of the <see cref="T:Client"/> class.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="accountNumber">The account number.</param>
      public Client(string name, string accountNumber)
      {
          _name = name;
          _accountNumber = accountNumber;
      }
       
      /// <summary>
      /// The Main entry point of the console application
      /// </summary>
      /// <param name="args">The command line arguments</param>
      static void Main(string[] args)
      {
          ArrayList clients = new ArrayList();
          clients.Add(new Client("Marco", "332-3355"));
          clients.Add(new Client("Martinus", "453-5662"));
          foreach (Client client in clients)
          {
              Console.WriteLine("The account {0} belongs to {1}", client.AccountNumber, client.Name);
          }
          Console.ReadLine();
      }
  }

这对大多数读者来说可能很熟悉,但是使用 ArrayList 我们不是类型安全的,在我看来这是不好的。我们还需要在想要使用它做一些事情时对对象进行强制类型转换(和取消类型转换)。对于值类型,还有另一个问题,我们不断地对列表中的对象进行装箱和取消装箱。

存储客户的更好方法是使用类型化集合,这将解决类型安全问题,我们也不必每次使用它时都强制类型转换检索到的对象。一个好方法是从 CollectionBase 类继承一个对象,看起来像这样

  /// <summary>
  /// Representation of a client
  /// </summary>
  public class Client
  {
      private string _name;
      private string _accountNumber;
       
      /// <summary>
      /// Gets or sets the account number.
      /// </summary>
      /// <value>The account number.</value>
      public string AccountNumber
      {
          get { return _accountNumber; }
          set { _accountNumber = value; }
      }
       
      /// <summary>
      /// Gets or sets the name.
      /// </summary>
      /// <value>The name.</value>
      public string Name
      {
          get { return _name; }
          set { _name = value; }
      }
       
      /// <summary>
      /// Initializes a new instance of the <see cref="T:Client"/> class.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="accountNumber">The account number.</param>
      public Client(string name, string accountNumber)
      {
          _name = name;
          _accountNumber = accountNumber;
      }
       
      /// <summary>
      /// The Main entry point of the console application
      /// </summary>
      /// <param name="args">The command line arguments</param>
      static void Main(string[] args)
      {
          ClientList clients = new ClientList();
          clients.Add(new Client("Marco", "332-3355"));
          clients.Add(new Client("Martinus", "453-5662"));
          foreach (Client client in clients)
          {
              Console.WriteLine("The account {0} belongs to {1}", client.AccountNumber, client.Name);
          }
          Console.ReadLine();
      }
  }
           
  /// <summary>
  /// A list of clients
  /// </summary>
  public class ClientList : CollectionBase
  {
      /// <summary>
      /// Adds the specified client to the list.
      /// </summary>
      /// <param name="client">The client.</param>
      /// <returns></returns>
      public int Add(Client client)
      {
          return List.Add(client);
      }
       
      /// <summary>
      /// Gets or sets the <see cref="T:Client"/> at the specified index.
      /// </summary>
      /// <value></value>
      public Client this[int index]
      {
          get
          {
              return (Client)List[index];
          }
          set
          {
              List[index] = value;
          }
      }
  }


这看起来比我们第一个例子中使用 ArrayList 要好得多,而且通常是一个好方法。但是如果您的应用程序发展,并且我们获得了更多想要存储在集合中的类型,例如 Account 或 bank。使用 1.1 框架,我们必须为我们使用的每种类型的对象创建一个新的集合类,或者退回到丑陋的 ArrayList 方法。但是,使用新的 2.0 框架,MS 添加了泛型。这使得创建使用类型参数的类和方法成为可能。这允许开发人员创建在定义和实例化类时推迟某些类型规范的类和方法。通过使用泛型类型参数,开发人员可以编写其他人可以使用而不会冒用未类型化类(如 ArrayList)所带来的风险的类,并且与创建类型化集合相比,减少了开发人员需要做的工作量。所以,让我们看看在使用框架中的泛型 List<T> 类时代码是什么样的。

  /// <summary>
  /// Representation of a client
  /// </summary>
  public class Client
  {
      private string _name;
      private string _accountNumber;
       
      /// <summary>
      /// Gets or sets the account number.
      /// </summary>
      /// <value>The account number.</value>
      public string AccountNumber
      {
          get { return _accountNumber; }
          set { _accountNumber = value; }
      }
       
      /// <summary>
      /// Gets or sets the name.
      /// </summary>
      /// <value>The name.</value>
      public string Name
      {
          get { return _name; }
          set { _name = value; }
      }
       
      /// <summary>
      /// Initializes a new instance of the <see cref="T:Client"/> class.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="accountNumber">The account number.</param>
      public Client(string name, string accountNumber)
      {
          _name = name;
          _accountNumber = accountNumber;
      }
       
      /// <summary>
      /// The Main entry point of the console application
      /// </summary>
      /// <param name="args">The command line arguments</param>
      static void Main(string[] args)
      {
          List<Client> clients = new List<Client>();
          clients.Add(new Client("Marco", "332-3355"));
          clients.Add(new Client("Martinus", "453-5662"));
          foreach (Client client in clients)
          {
              Console.WriteLine("The account {0} belongs to {1}", client.AccountNumber, client.Name);
          }
          Console.ReadLine();
      }
  }

感谢泛型,我们创建了一个类型安全的集合,就像我们通常实例化 ArrayList 一样简单,而无需编写我们自己的类型化集合。现在我们已经简要地了解了如何使用泛型来减少我们需要编写的代码量,同时仍然使用类型化集合,让我们看看如何创建我们自己的自定义泛型类。为了演示这一点,我创建了一个示例类,它继承自 DictionaryBase 类。这个字典类的实现将接受 GUID 作为键,并将类型参数作为值类型。如果你和我一样,你会使用 GUID 来标识数据库中的记录,所以使用它作为类型化集合中的键是我很喜欢做的事情。所以现在我有了可以用来存储从数据库中检索数据时创建的所有对象的酷炫字典,我会给你代码。

  /// <summary>
  /// Representation of a client
  /// </summary>
  public class Client
  {
      private string _name;
      private string _accountNumber;
       
      /// <summary>
      /// Gets or sets the account number.
      /// </summary>
      /// <value>The account number.</value>
      public string AccountNumber
      {
          get { return _accountNumber; }
          set { _accountNumber = value; }
      }
       
      /// <summary>
      /// Gets or sets the name.
      /// </summary>
      /// <value>The name.</value>
      public string Name
      {
          get { return _name; }
          set { _name = value; }
      }
       
      /// <summary>
      /// Initializes a new instance of the <see cref="T:Client"/> class.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="accountNumber">The account number.</param>
      public Client(string name, string accountNumber)
      {
          _name = name;
          _accountNumber = accountNumber;
      }
       
      /// <summary>
      /// The Main entry point of the console application
      /// </summary>
      /// <param name="args">The command line arguments</param>
      static void Main(string[] args)
      {
          GuidDictionary<Client> clients = new GuidDictionary<Client>();
          Guid clientID1 = Guid.NewGuid();
          Guid clientID2 = Guid.NewGuid();
          clients.Add(clientID1, new Client("Marco", "332-3355"));
          clients.Add(clientID2, new Client("Martinus", "453-5662"));
          Console.WriteLine("The account {0} belongs to {1}", clients[clientID1].AccountNumber, clients[clientID1].Name);
          Console.WriteLine("The account {0} belongs to {1}", clients[clientID2].AccountNumber, clients[clientID2].Name);
          Console.ReadLine();
      }
  }
       
  public class GuidDictionary<T> : DictionaryBase
  {
      public void Add(Guid id, T item)
      {
          Dictionary.Add(id, item);
      }
       
      public T this[Guid id]
      {
          get
          {
              return (T)Dictionary[id];
          }
          set
          {
              Dictionary[id] = value;
          }
      }
  }

好吧,它可能不像你预期的那样棒,标准字典中的许多方法甚至没有实现,但是嘿,我必须留一些有趣的事情让你这个读者去做。

那么我们在上面的代码中到底做了什么?我们创建了一个由 Guid 索引的字典,并使用类型参数来限制我们的 add 方法和索引器。现在,当我们创建一个新类的实例并指定类型参数时,我们便拥有了一个类型安全的字典,该字典可用于按给定的 Guid 存储和检索对象的类型。

声明中的 T 只是一个名称,我们也可以使用 VeryLongNameForTheTypeParameter 而不是 T。好的,我们已经看到了如何使用类型参数来创建泛型类。但是在我们进入下一部分之前,让我们看看 System.Collections.Generic.Dictionary<>。此类必须通过提供名为 TKey 和 TValue 的两个类型参数来实例化。这表明我们可以在定义中使用多个类型参数,同时也表明构建自己的字典类是浪费时间,我可以很容易地使用像这样泛型字典:Dictionary<Guid, Client> 并完成它。


附件:构建自定义属性

此页面原文由 William "Scott" Baker 撰写

属性摘要

属性是一种“标记”代码元数据的元素的方法,它使用描述性信息,这些信息可以在运行时使用反射访问。属性必须直接或间接地从 System.Attribute 派生。.NET Framework 中存在大量的属性;你也可以定义自己的属性。在代码中使用属性有三个方面

  1. 定义自定义属性类,这涉及
    1. AttributeUsageAttribute属性分配给你的类。
    2. 编写代码以定义你的自定义属性类。
    3. 为你的类创建参数。
  2. 将属性分配给代码成员。
  3. 在运行时检索属性信息。

创建自定义属性类

如前所述,.NET Framework 中有几个预定义的属性;你可能已经在代码中使用过它们。特别是 XML 解析器在(反)序列化对象时严重依赖属性。你也可以定义自己的自定义属性,正如我们将在这里展示的那样。定义自定义属性涉及三个步骤

  1. AttributeUsageAttribute属性分配给你的类。
  2. 编写代码以定义你的自定义属性类。
  3. 为你的类创建参数。

"AttributeUsageAttribute"分配给你的类

你的属性的“范围”和其他特征应通过使用AttributeUsageAttribute属性来指定。
注意:在 Visual Basic 中,使用AttributeUsageAttribute属性对于所有自定义属性是必需的。将AttributeUsageAttribute属性应用于类
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
public Class QualityCheckAttribute : System.Attribute 
{
  // ...
}

注意使用“AttributeUsage" 与 "。AttributeUsageAttribute". 按照惯例,所有属性都以 "Attribute" 后缀命名 - 但在代码中使用时,可以省略后缀。这也适用于用户定义的属性;QualityCheckAttribute属性可以作为以下两种方式引用

[QualityCheck] // or... 
[QualityCheckAttribute]

AttributeUsageAttribute有三个成员ValidOn、AllowMultipleInherited.

  • ValidOn成员接受AttributeTargets枚举值,并将你的属性限制为你指定的代码类型。默认值为AttributeTargets.All. 你可以将你的属性限制为类、枚举、返回值或以下列表中的任何一个
All (any element)   Delegate    GenericParameter    Parameter
Assembly            Enum        Interface           Property
Class               Event       Method              ReturnValue
Constructor         Field       Module*             Struct

*Module refers to a portable executable (.exe or .dll), and not a Visual Basic standard module.

你也可以将目标类型组合为按位 OR 操作,以指定多个可接受的值

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
  • AllowMultiple是一个布尔值,它确定是否可以将属性多次应用于给定的成员。默认值为false. 以下示例说明了同一个属性在代码元素上的多次实例
[QualityCheck("Scott Baker", "02/28/06", IsApproved = true,
   Comment = "This code follows all established guidelines.  Release approved.")]
[QualityCheck("Matt Kauffman", "01/15/06", IsApproved = false,
   Comment = "Code quality much improved. Minor revision required.")]
[QualityCheck("Joe Schmoe", 01/01/06", IsApproved = false,
   Comment = "This code is a mess and needs a complete rewrite")]
public class MyClass
{
// ... 
}
  • Inherited成员确定是否将继承类中设置的属性。默认值为 true
[AttributeUsage(AttributeTargets.Class)]
public class AttrOneAttribute : Attribute
{
  // ... 
}

// This attribute will not be inherited 
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class AttrTwoAttribute : Attribute
{
  // ... 
}

[AttrOne]
[AttrTwo]
public class ClassOne 
{
  // ... 
}

// This class inherits AttrOne from ClassOne, 
// but not AttrTwo 
public class ClassTwo : ClassOne
{
  // ... 
}

定义自定义属性类

  • 属性是继承自System.Attribute的类,直接或间接继承
public Class QualityCheckAttribute : System.Attribute // direct 
{
  // ...
}

public Class FinalCheck : QualityCheckAttribute  // indirect 
{
// ...
}
  • 属性类具有AttributeUsageAttributeattribute
[AttributeUsage(AllowMultiple = true, Inherited = false)]
public Class QualityCheckAttribute : System.Attribute
{
  // ...
}

属性。如前所述,使用AttributeUsageAttribute属性在 VB 中是必需的。在 C# 中,如果未声明,它将自动应用默认值。

为属性创建参数

属性接受两种类型的参数:位置参数和命名参数。位置参数由类中的公共构造函数定义,必须按定义的顺序排列,并且是必需的;命名参数由公共属性定义,可以按任何顺序排列,并且是可选的。
位置参数
属性的位置参数由类中的构造函数定义。与任何类一样,构造函数可以重载,并且可以定义不接受任何参数的默认构造函数。与任何其他类一样,位置参数的签名必须与属性类中的构造函数的签名匹配
public class QualityCheckAttribute : Attribute
{
  public QualityCheckAttribute(string Name, string Date)
  // ... 
}
命名参数
属性的命名参数由类中定义的公共属性定义。命名参数是可选的,并且声明任何位置参数之后。该IsApproved属性演示了一个命名参数
public class QualityCheckAttribute : Attribute
{
  private string _name;
  private string _date;
  private bool isApproved;
  public bool IsApproved
  {
    get {return isApproved;}
    set {isApproved = value;}
  }
  public QualityCheckAttribute(string Name, string Date)
  {
    // ...
  }
}

请记住,代码中的变量可以是位置参数,也可以是命名参数。如果要为_name_date 字段添加公共属性,我们可以将其用作命名参数或位置参数。当然,这不是一个推荐的做法:必需的参数应该是位置参数,可选参数应该是命名参数。

将属性分配给代码成员

你已经看到了将属性分配给代码成员的示例。但是,必须澄清一些要点。

  • 消歧义是指在代码成员上使用属性的澄清。
  • 语法 - 有多种方法可以应用多个属性。

消歧义

以下代码不清楚SomeAttribute是否应用于方法MyMethod或其return
public class MyAttribute : Attribute
{
  [SomeAttribute("Hello")]
  public string MyMethod(aString)
  {
    return aString;
  }
}

消歧义解决了这些问题。通过指定属性应用到的代码类型,我们能够解决混乱。以下代码显示属性应用于return

public class MyAttribute : Attribute
{
  [return : SomeAttribute]
  public string MyMethod(aString)
  {
    return aString;
  }
}

下表列出了允许使用属性的所有声明;对于每个声明,在第二列中列出了该声明上属性的可能目标。以粗体显示的目标是默认值。

Declaration               Possible targets
assembly                  assembly
module                    module
class                     type
struct                    type
interface                 type
enum                      type
delegate                  type, return
method                    method, return
parameter                 param
field                     field
property — indexer        property
property — get accessor   method, return
property — set accessor   method, param, return
event — field             event, field, method
event — property          event, property
event — add               method, param
event — remove            method, param

*Reference: Disambiguating Attribute Targets (C# Programming Guide)

人们可能会认为AttributeUsageAttribute 的 AttributeTargets在属性定义中将有助于防止这种混淆:人们会错了。编译器在解决冲突时不会使用AttributeUsageAttribute信息。即使你将属性定义为仅应用于特定类型,例如AttributeTargets.Return,你仍然必须明确它应用于return类型,当应用属性时,否则编译器将使用默认目标method类型,并抛出错误。

语法:应用多个属性

当将多个属性应用于成员时,有两种方法可以做到这一点
[AttrOne(...), AttrTwo(...)]  
  // or...
[AttrOne(...)]
[AttrTwo(...)]

这两种方法是等效的。请记住,如果你要在单个大括号中指定多个属性,它们必须应用于相同的目标类型。如果没有,你必须为每个类型提供单独的声明

[return : AttrOne(...), method : AttrTwo(...)]  // <-- invalid!
  // instead, you must...
[return : AttrOne(...)]
[method : AttrTwo(...)]

在运行时检索属性信息

能够声明和应用属性并不是很有用,除非我们可以检索这些数据并使用它们。幸运的是,这是一个直接的过程。将解决三个基本场景

  1. 从成员中检索单个属性。
  2. 从成员中检索多个属性。
  3. 从多个成员中检索单个类型的属性。

从成员中检索单个属性

要访问属性信息

  1. 声明属性类型的实例。
  2. 使用Attribute.GetCustomAttribute(type, typeof)方法将属性读入实例。
  3. 使用实例的属性读取值。

以下示例代码声明了一个类ExampleClass,它具有QualityCheck属性。该GetSingleAttribute方法接受目标成员类型和要查找的属性类型。该Attribute.GetCustomAttribute方法将属性信息检索到attr对象中,我们可以在其中读取非常重要的IsApprovedproperty

[QualityCheck("Scott Baker", "02/04/2006", IsApproved = false)]
public class ExampleClass
{

  public static void Main()
  {
    GetSingleAttribute(typeof(ExampleClass), typeof(QualityCheck))
  }
 
  public static void GetSingleAttribute(Type targetType, Type attrType)
  {
    typeof(attrType) attr = (attrType)Attribute.GetCustomAttribute(targetType, typeof(attrType));
  
    if (attr == null)
    { //... }
    else
    {
      Console.Writeline(attr.IsApproved);
    }
  }

属性。需要注意的一个重要因素是GetCustomAttribute方法旨在读取一个且仅一个属性。GetCustomAttribute实际上检查是否有多个属性匹配 - 如果没有匹配,它将返回null,但如果有多个匹配,它将抛出AmbiguousMatchException异常。在检查属性时,唯一可以安全使用GetCustomAttribute方法的情况是属性定义声明[AttributeUsage(AllowMultiple=false)].

从成员中检索多个属性

读取成员上的多个属性实例与读取一个属性没有什么不同;要读取多个属性,请使用复数形式的GetCustomAttributes方法,该方法返回一个属性数组。然后,你可以遍历结果数组并读取值

QualityCheck[] attrArray = (QualityCheck[])Attribute.GetCustomAttributes(t, typeof(QualityCheck));

foreach (QualityCheck attr in attrArray)
{
  Console.Writeline(attr.IsApproved);
}

从多个成员中检索单个属性类型

如果你想做一些更复杂的事情,比如检查类中的每个方法是否存在 QualityCheck 属性,该怎么办?由于System.Reflection命名空间的存在,我们甚至不必费力。只需将所有成员(在本例中为方法)读入MemberInfo数组并遍历它们

using System.Reflection

public class ExampleClass
{
  public static void Main()
  {
    RetrieveAttributes(typeof(ExampleClass));
  }

  public void RetrieveAttributes(Type t)
  {
    MemberInfo[] methodList = t.GetMethods();
    foreach (MemberInfo m in methodList)
    {
      QualityCheck[] attrArray = (QualityCheck[])Attribute.GetCustomAttributes(m, typeof(QualityCheck));
      foreach (QualityCheck attr in attrArray)
      {
        Console.Writeline(attr.IsApproved);
      }
    }
  }
}
华夏公益教科书