第三课:构建类
在面向对象的语言中,大量的工作要在对象里去做。除了最简单的应用所有的工作都需要构造一个或多个自定义的类,每个类中有多个属性和方法被用于去完成对象的任务。这课讨论如何创建自定义的类。
学完这课,你将能够:
■ 描述和使用继承.
■ 描述和使用接口.
■ 描述和使用部分类.
■ 创建泛型类型和使用内建的泛型类型。
■ 响应事件和引发事件。
■ 添加属性去描述程序集和方法。
■ Move a type from one class library to another using type forwarding.
什么是继承?
.NET Framework有几千个类,每个类有许多方法和属性。如果.NET Framework没有被非常一致性地实现,保持跟踪所有的类和成员是不可能的。举个例子来说,每个类都有一个ToString 方法,它做了一个事—把类的实例转化为字符串。相似的,很多类支持相同的操作,比如比较两个实例是否相等。因为继承和接口使一致性成为可能。使用继承从已存在的类去创建新类。你能通过继承System.Application
Exception去创建自定义异常。
class DerivedException : System.ApplicationException
{
public override string Message
{
get { return "An error occurred in the application."; }
}
}
你能抛出和捕捉新的异常因为这个自定义类继承了基类的行为,如下:
try
{
throw new DerivedException();
}
catch (DerivedException ex)
{
Console.WriteLine("Source: {0}, Error: {1}", ex.Source, ex.Message);
}
自定义异常不仅支持throw/catch,且可使用包括继承自System.Application-
Exception的成员。继承的另一个好处是能够相互交换地使用继承类,使功能得到扩展。
什么是接口?
接口,如同合同,定义了一系列的类的成员,这些类实现了接口提供的成员。例如,
IComparable 接口定义了CompareTo方法,它定义了两个类的实例能够去比较判断是否相等。所有的类无论是内建的还是自定义的只要实现了IComparable接口,就可以比较判断是否相等。接口IDisposable提供了一个方法Dispose,使创建类的实例的程序集去释放实例消费的任何资源。去创建一个实现IDisposable接口的类:
1. 类声明
// C#
class BigClass
{
}
2. 加入接口声明
class BigClass : IDisposable
{
}
类 描述
IComparable 实现它的类型可以排序,例如,数字和字符串类。
IComparable 被要求排序。
IDisposable 定义了一些通过手动去销毁一个对象。这个接口对于消费资源的大对象
是很重要的,或锁住对资源的访问比如数据库。
IConvertible 使一个类转换为一个基类型,例如Boolean, Byte, Double,String.
ICloneable 支持对象的拷贝。
IEquatable 允许你去比较一个类的实例为了判断是否相等,例如如果你实现这个接口 1 你能说是否(a == b)”.
IFormattable 能使你把一个对象的值转化为一个特定格式的字符串,它比ToString方法
提供了更多的灵活性。
你也能创建你自己的接口。如果你需要创建多个自定义类,并且它们行为相似并且能交换使用,那么定义类是有用的。
什么是泛型?
泛型是.NET Framework 的类型系统的一部分,它允许你定义一个类型而不用考虑一些细节。不用去定义参数类型或成员类型,你能够允许代码使用你的类型去定义它。这样它能使客户代码去修正你的类型去满足它自己的特定的需要泛型是在.NET 2.0中新出现的。.NET Framework version 2.0中的System.Collections.Generic namespace包括几个泛型集合类包括Dictionary, Queue, SortedDictionary, 和 SortedList.这些类的功能与它们在System.Collections 中的对应的集合类相同,但是泛型类提供了更好的性能和类型
为什么用泛型?
.NET Framework 的Versions 1.0 and 1.1 不支持泛型.开发者只能使用Object 类 作为参数和成员并且要把Object 类和其它类型相互转化。
相对于用Object类泛型有两个好处:
■ 减少运行时错误:当你在做Object 类和其它类型相互转化时编译器不能检测到类型错误。例如,如果你把一个字符串转换为一个Object 类 然后试图把Object 类转换为一个整形,编译器无法捕捉这个错误,于是运行时将会抛出一个异常。使用泛型允许编译器在你的程序运行之前去捕捉类型错误。同时,你能定义约束去限制一个泛型中使用的类型,使编译器能够探测不相容的类型。
■ 更高的性能:类型转换要求装箱和拆箱(boxing and unboxing),这个过程占用了进程的时间和降低了性能。使用泛型不要求类型转换和装箱(boxing and unboxing),这样做提升了运行时的性能。
真实的世界
我从没有感受到泛型带来的性能提升,但是,根据微软所说的,泛型比使用类型转换的速度更快。事实上,类型转换类型转换的速度是使用泛型速度的几倍。但是你可能在你的应用中不注意性能差别。所有由于它能带来类型安全,所以仍然要用泛型。
如何创建一个泛型类型
首先,测试以下的类。Obj类和Gen类做了相同的事情,但是Obj类使用Object类去赋任何值,而Gen类使用泛型:
class Obj
{
public Object t;
public Object u;
public Obj(Object _t, Object _u)
{
t = _t;
u = _u;
}
}
class Gen<T, U>
{
public T t;
public U u;
public Gen(T _t, U _u)
{
t = _t;
u = _u;
}
}
如你所见, Obj 类有两个Object类型的成员。Gen类有两个类型T和U的成员。依赖你是如何使用代码去使用Gen 类,T 和U 可以是一个string,int,自定义类等等。
创建一个泛型有一个重要的限制:泛型代码仅仅在泛型每种可能构造的实例都能编译通过,泛型代码才是有效的。事实上,当你写泛型代码时,你被限制在Object类的能力中。因此,你能够在你的类中调用ToString或GetHashCode方法,但是不能使用+ 或 > 操作符。这些限制并不能应用到客户代码中,因为它已经为泛型声明了一个类型。
如何使用一个泛型类型
考虑一下console 应用代码,使用了Gen 和 Obj 类:
// Add two strings using the Obj class
Obj oa = new Obj("Hello, ", "World!");
Console.WriteLine((string)oa.t + (string)oa.u);
// Add two strings using the Gen class
Gen<string, string> ga = new Gen<string, string>("Hello, ", "World!");
Console.WriteLine(ga.t + ga.u);
// Add a double and an int using the Obj class
Obj ob = new Obj(10.125, 2005);
Console.WriteLine((double)ob.t + (int)ob.u);
// Add a double and an int using the Gen class
Gen<double, int> gb = new Gen<double, int>(10.125, 2005);
Console.WriteLine(gb.t + gb.u);
如果你在console应用中运行代码,Obj类和Gen类执行的结果是相同的。但是,使用Gen类的代码事实上工作起来会更快,因为它不要求装箱和拆箱。还有,开发者使用Gen类时会更省事。首先,开发者不用手动地去把Object类转化为合适的类型。其次,类型错误会在编译时期被捕捉而不是运行时捕捉。为了显示它的好处,考虑以下代码,它包含一个错误:
Gen<double, int> gc = new Gen<double, int>(10.125, 2005);
Console.WriteLine(gc.t + gc.u);
// Add a double and an int using the Obj class
Obj oc = new Obj(10.125, 2005);
Console.WriteLine((int)oc.t + (int)oc.u);
代码的最后一行有一个错误-- oc.t的值转换成int型而不是double型。不幸的是,编译器没有捕捉到这个错误。而运行时在试图把double型转化为int型时捕捉到这个异常。
如何使用约束:
如果你写的代码是为任何类型编译通过的,那么泛型将会受到极大限制,因为你将被限制在基类Object所能做到的范围内。为了克服这个限制,使用约束去规定类型。
泛型支持四种约束类型:
■ Interface:仅仅允许实现了特点接口的类型去使用你的泛型
■ class: 仅仅允许匹配或继承了特定基类的类型去使用你的泛型
■ Constructor :要求你的类型必须实现一个无参数的构造方法.
■ Reference or value:要求类型不是引用类型就是值类型。
下面的泛型类仅仅被实现了IComparable接口的类型使用:
class CompGen<T>
where T : IComparable
{
public T t1;
public T t2;
public CompGen(T _t1, T _t2)
{
t1 = _t1;
t2 = _t2;
}
public T Max()
{
if (t2.CompareTo(t1) < 0)
return t1;
else
return t2;
}
}
以上的类将会正确地被编译。但,如果删除where子句,编译器将返回一个错误,暗示T不能包括一个CompareTo的定义。通过约束泛型使类型必须要实现IComparable,你要保证CompareTo方法能够有效。
事件
大部分的工程是非线性的。在Windows Form 应用中,你可能必须等待一个用户去点击一个按钮或按一个键,然后对这个事件做出相应。在服务器应用中,你可能必须等待一个网络请求。这些能力通过.NET Framework中的事件来提供。
什么是事件?
一个事件是一个对象发出的消息,表明一个行为的发生。这个行为被用户的交互行为所引发,比如一个鼠标的点击,或鼠标被其它应用程序所触发。引发事件的对象被称为event sender.捕捉事件的对象并且对它进行响应的对象被称为event receiver。在事件交互中,event sender类不知道哪个对象或方法将会收到(或处理)它所引发的事件。需要的是一个在源与接受器之间的媒介物(类似于指针的机制)。.NET Framework 定义了一个特殊的类型(Delegate),它能提供一个函数指针的功能。
什么是Delegate?
一个delegate是一个方法的引用。不像其它的类,一个delegate类有一个签名,并且它仅能抓住匹配delegate签名的方法的引用。一个delegate等同于类型安全的方法指针或回调。Delegate有其他的很多用途,这里只讨论delegate的事件处理的功能。一个delegate声明对于定义一个delegate类足够了。声明提供了delegate的签名,公共语言运行时提供了执行。以下的例子显示了一个事件代理的声明:
public delegate void AlarmEventHandler(object sender, EventArgs e);
标准的事件句柄的签名定义了一个方法,这个方法不会返回一个值,它的第一个参数是Object类型并且指向引发一个事件的实例,第二个参数是继承EventArgs类型并且抓住了事件数据。如果事件没有生成事件数据,第二个参数仅仅是一个EventArgs的一个实例。否则,第二个参数要继承EventArgs并且提供属性或变量去抓住事件数据。
EventHandler是一个已定义好的delegate,它代表一个事件的处理方法, 并且这个事件不应生成数据。如果你的事件生成了数据,你必须提供你自己定义的事件数据类型,同时你要创建一个delegate,在delegate中的第二个参数是你自己定义的类型,或用泛型的EventHandler delegate把泛型参数换成你的定义的参数类型。
为了让处理事件的方法与事件作关联,向事件加一个delegate的实例。这个事件处理器会在事件发生时被调用,除非删除这个delegate。
如何响应一个事件
你必须做两件事去响应一个事件:
■ 创建一个方法去响应事件。这个方法必须匹配Delegate 签名。典型地,这意味着它必须返回空置并且接受两个参数:
一个 Object类型和一个EventArgs (或其基类)。
以下代码所示:
private void button1_Click(object sender, EventArgs e)
{
// Method code
}
■ 加入事件处理器去指示哪个方法应当接收事件,如下代码:
this.button1.Click += new System.EventHandler(this.button1_Click);
NET Framework 2.0 包括一个新出现的EventHandler的泛型版本
当事件被引发时,你定义的方法将会运行。
如何引发一个事件
为了引发一个事件你必须做三件事:
■创建一个代理
public delegate void MyEventHandler(object sender, EventArgs e);
■ 创建一个事件成员
public event MyEventHandler MyEvent;
■ 当你需要引发这个事件时,在一个方法中调用代理:
MyEventHandler handler = MyEvent;
EventArgs e = new EventArgs();
if (handler != null)
{
// Invokes the delegates.
handler(this, e);
}
// Note that C# checks to determine whether handler is null.
还有,如果你需要向事件句柄传递信息是,可以写一个继承EventArgs 类的自定义类。
什么是Attributes?
Attributes可以描述一个type,method,或property,并使之能够通过使用Reflection的技术去用程序查询到它们。一般用途如下:
■ 定义一个类要求的安全权限
■ 定义安全权限去拒绝降低安全风险。
■ 什么功能,比如支持序列化。
■ 通过提供标题,描述和版权信息去描述一个assembly
Attribute 类型都从System.Attribute base class 继承并且要写在 []里. 以下代码展示如何去加入assembly attributes :
[assembly: AssemblyTitle("ch01cs")]
[assembly: AssemblyDescription("Chapter 1 Samples")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft Learning")]
[assembly: AssemblyProduct("ch01cs")]
[assembly: AssemblyCopyright("Copyright © 2006")]
[assembly: AssemblyTrademark("")]
Visual Studio 在你创建一个项目的时候,为你的assembly自动地创建一些标准的attributes, 包括标题,描述,公司,指南,和版本。你应当为你创建的工程去编辑这些attributes 因为默认不会包含一些重要的信息比如描述。
为了使一个类能够被序列化你必须加入Serializable attribute ,代码如下:
[Serializable]
class ShoppingCartItem
{
}
没有Serializable attribute, 一个类不能够被序列化。类似的,下面的代码使用
attributes 去声明它需要读C:"boot.ini 文件. 因为这个attribute的存在,如果它没有这个特定的权限,runtime 将在执行之前抛出一个异常:
using System;
using System.Security.Permissions;
[assembly:FileIOPermissionAttribute(SecurityAction.RequestMinimum, Read=@"C:"boot.ini")]
namespace DeclarativeExample
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
什么是类型传递?
类型传递是一个属性(在TypeForwardedTo中实现),它允许你将一个类型从一个程序集(程序集A)移动到另一个程序集(程序集B),并且在客户端实例化程序集A时不需要重新编译,就可以运行。在一个组件(程序集)载入并被客户端应用程序使用后,你可以用类型传递将组件中一个类型移动到另一个程序集,而客户端应用程序仍将保持工作,不需要重新编译。类型传递只能使用在从已存在的应用程序引用的组件。当你重新编译一个应用程序时,在应用程序中使用的任何类型都必须是恰当的程序集引用(这个程序集已存在)。
下面这些步骤用来将一个类型从一个类库移动到另一个。
1.添加一个TypeForwardedTo属性到来源程序集类库。
2.将类型声明代码剪切
3.将剪切的类型声明代码粘贴到目的类库。
4.编译两个类库
下面代码示范将TypeA移动到DestLib类库的属性声明。
using System.Runtime.CompilerServices;
[assembly:TypeForwardedTo(typeof(DestLib.TypeA))]
分享到:
相关推荐
4. **对象和类**: 面向对象编程(OOP)是C#的核心特性,第一章会详细解释类的概念,包括类的定义、属性、方法、构造函数和析构函数,以及对象的创建和使用。 5. **继承与多态**: 继承是OOP中的重要概念,允许一个类...
第3章 命名规范 3.1 大小写约定 3.1.1 标识符的大小写规则 3.1.2 首字母缩写词的大小写 3.1.3 复合词和常用术语的大小写 3.1.4 是否区分大小写 3.2 通用命名约定 3.2.1 单词的选择 3.2.2 使用单词...
#### 第一章:介绍ADO.NET 4.0实体框架 **知识点概述:** 本章节主要介绍了ADO.NET 4.0实体框架的基本概念及其在.NET编程中的重要性。ADO.NET 4.0实体框架是一种用于与数据库进行交互的对象模型和工具集,它提供了...
.NET Framework是微软开发的一个软件框架,它为开发者提供了一个一致的编程环境,使得构建、部署和运行基于Windows的应用程序变得更加简单。C#是.NET Framework的主要编程语言,它结合了面向对象编程的特性,如封装...
这个压缩包包含了第一章至第三章的上机实践阶段的源代码以及指导学习1阶段的练习代码,对于想要自学或者巩固C#编程技能的人来说,是一份非常实用的学习资料。 首先,我们来详细了解这三个章节的知识点: **第一章...
在"ASP.NET技术开发网上书店 第一章上机作业"中,我们可以推测这是一份教学材料或课程实践,旨在帮助学习者掌握ASP.NET的基础知识,并应用到实际的网上书店项目中。 第一章通常会介绍基础概念和环境搭建,让我们...
在本门课程的第一章中,重点是了解如何使用 C# 编写命令行和窗口程序,以及如何通过 ADO.NET 进行数据库交互。以下是一些关键知识点: 1. **C# 程序开发步骤**:学习如何创建、编译和运行 C# 程序。这包括使用 ...
在第一章,我们将介绍 Zend Framework 的设计理念,包括MVC(模型-视图-控制器)模式、依赖注入、面向对象编程等原则。此外,还会讨论 Zend Framework 如何与其他PHP库和框架集成,以及如何利用其强大的组件化设计来...
.NET Framework是微软开发的一个全面的开发平台,它包含了运行应用程序所需的各种组件和服务。这个平台的主要语言之一是C#,一种面向对象的编程语言,广泛应用于Windows应用开发、Web服务和移动应用等领域。以下是对...
1. **图形用户界面的基本原理**:GUI环境提供了更直观的交互方式,用户通过鼠标操作各种控件,如按钮、文本框、标签和组合框等,进行信息输入和交互。 2. **GUI的优势**:GUI降低了学习和使用的难度,大部分操作可...
- **第3章:模板引擎** - **Jinja2模板系统**:介绍Flask默认使用的模板引擎Jinja2的基本语法和高级特性。 - **上下文处理**:讲解如何在模板中使用全局变量以及传递动态数据。 - **第4章:表单处理** - **表单...
- **第3章**:第一个 MVC 应用程序——通过实践建立第一个应用,了解 MVC 架构的基本组成。 - **第4章**:MVC 模式——深入理解 MVC 设计模式,探讨其在 ASP.NET MVC 3 中的应用。 - **第5章**:必需的语言特性...
- **框架类库(FCL)**:提供了一系列预定义的类,这些类可以帮助开发者快速构建各种类型的.NET应用程序,包括桌面应用、Web应用以及移动应用等。 #### 二、.NET的战略目标 .NET的目标非常明确,即实现“三W”原则...
#### 第一章:认识ADO.NET Entity Framework **1.1 实体关系模型:编程面向模型而非数据库** - **定义与作用**:实体关系模型是EF的核心之一,它允许开发者基于模型进行编程,而不是直接针对数据库表和列。这种方式...
- **第3章:理解控制器与动作** - **控制器**:处理用户请求并返回响应的核心组件。 - **动作方法**:控制器内的方法,每个方法对应一个特定的功能。 - **HTTP动词支持**:GET、POST等HTTP动词的使用场景。 - **...
总之,ASP.NET课本第一章是初学者探索Web开发领域的理想起点,它将引导你进入ASP.NET的世界,帮助你理解Web Forms的开发流程,以及如何使用ASP.NET创建动态的Web应用程序。通过实践课本中的示例,你将能够快速上手并...
#### 一、第一章 开始使用实体框架 **1-1 实体框架简述** - **概念**:实体框架(Entity Framework,简称EF)是一种由Microsoft开发的对象-关系映射(ORM)框架,它允许开发者将应用程序中的对象模型与数据库中的...
第3章 命名规范 3.1 大小写约定 3.1.1 标识符的大小写规则 3.1.2 首字母缩写词的大小写 3.1.3 复合词和常用术语的大小写 3.1.4 是否区分大小写 3.2 通用命名约定 3.2.1 单词的选择 3.2.2 使用单词...