当前位置:首页 >> 理学 >>

C#.NET基础知识


一:体系结构 .................................................................................................................................. 2 .NET Framework 平台体系结构 [C#] ........................................................................ 2 栈和托管堆/值类型和引用类型 强制类型转换/装箱和拆箱 值类型和引用类型/强制类型转换 装箱和拆箱[C#] ................................ 3 栈和托管堆 值类型和引用类型 强制类型转换 装箱和拆箱 一, 栈和托管堆....................................................................................................... 4 二,类型层次结构........................................................................................................... 5 三,引用类型................................................................................................................... 6 四,预定义的引用类型 ................................................................................................... 7 五,强制类型转换 ........................................................................................................... 8 六,装箱和拆箱(boxing/unboxing).......................................................................... 9 二:方 法 ....................................................................................................................................... 10 覆盖与重载(override/overload) [C#]........................................................................... 10 1. 方法签名与方法的显式隐藏 ................................................................................... 11 2. 方法重载与重写(overload & override) ............................................................ 21 抽象方法和虚方法的区别 [C#] ......................................................................................... 22 深入剖析 C#的多态 [C#] .................................................................................................. 22 一,什么是多态............................................................................................................. 23 二,实现多态................................................................................................................. 24 三:委托与事件............................................................................................................................. 26 委托和事件 [C#] ................................................................................................................. 26 引 言............................................................................................................................... 27 将方法作为方法的参数 ................................................................................................. 27 将方法绑定到委托 ......................................................................................................... 32 事件的由来..................................................................................................................... 35 事件和委托的编译代码 ................................................................................................. 41 委托,事件与 Observer 设计模式............................................................................. 43 委托模型和事件模型 [C#] ................................................................................................. 53 1.多播委托 .................................................................................................................. 53 2. 事 件 .................................................................................................................... 56 3.用户定义的事件 ......................................................................................................... 61 索引器(indexer) [C#] ....................................................................................................... 66 1.索引器的作用是什么?............................................................................................... 66 2. 索引器的使用难吗?................................................................................................. 67 3.怎样定义一个索引器?............................................................................................... 67 4. 使用索引器............................................................................................................... 68 反射(Reflection) [C#] ...................................................................................................... 75 四:字符串..................................................................................................................................... 78 1.string 与 String 的区别 .................................................................................................. 78 2. String 与 StringBuilder 的区别 .................................................................................. 79 3.string 和 StringBuilder 性能比较 ................................................................................ 80 五:类与接口................................................................................................................................. 82 抽象类和接口的区别 [C#] ................................................................................................. 82 一,抽象类..................................................................................................................... 83 二 ,接 口 ..................................................................................................................... 85 三,抽象类和接口 ......................................................................................................... 87

四,其它文章................................................................................................................. 89 C#2.0 中的静态类 static class [C#] ............................................................................. 92 静态类的限制................................................................................................................. 93 六:工具集使用............................................................................................................................. 94 MSIL 反汇编程序 (Ildasm.exe) [C#] .......................................................................... 94

一:体系结构

.NET Framework 平台体系结构 [C#] C# 程序在 .NET Framework 上运行,它是 Windows 的一个必要组件,包括一个称为 公共语言运行时 (CLR) 的虚拟执行系统和一组统一的类库. CLR 是 Microsoft 的公共语 言基础结构 (CLI) 的一个商业实现.CLI 是一种国际标准,是用于创建语言和库在其中无 缝协同工作的执行和开发环境的基础.

用 C# 编写的源代码被编译为一种符合 CLI 规范的中间语言 (IL).IL 代码与资源(如 位图和字符串)一起作为一种称为程序集的可执行文件存储在磁盘上,通常具有的扩展名 为 .exe 或 .dll.程序集包含清单,它提供关于程序集的类型,版本,区域性和安全要求 等信息.

执行 C# 程序时, 程序集将加载到 CLR 中, 这可能会根据清单中的信息执行不同的操作. 然后,如果符合安全要求,CLR 执行实时 (JIT) 编译以将 IL 代码转换为本机机器指令. CLR 还提供与自动垃圾回收,异常处理和资源管理有关的其他服务.由 CLR 执行的代码 有时称为"托管代码",它与编译为面向特定系统的本机机器语言的"非托管代码"相对应.下 图演示了 C# 源代码文件,基类库,程序集和 CLR 的编译时与运行时的关系.

语言互操作性是 .NET Framework 的一个关键功能.因为由 C# 编译器生成的 IL 代码 符合公共类型规范 (CTS),因此从 C# 生成的 IL 代码可以与从 Visual Basic,Visual C++,Visual J# 的 .NET 版本或者其他 20 多种符合 CTS 的语言中的任何一种生成 的代码进行交互.单一程序集可能包含用不同 .NET 语言编写的多个模块,并且类型可以 相互引用,就像它们是用同一种语言编写的.

除了运行时服务,.NET Framework 还包含一个由 4000 多个类组成的内容详尽的库, 这些类被组织为命名空间, 为从文件输入和输出到字符串操作, XML 分析, Windows 到 到 窗体控件的所有内容提供多种有用的功能.典型的 C# 应用程序使用 .NET Framework 类库广泛地处理常见的"日常"任务.

栈和托管堆/值类型和引用类型 强制类型转换 装箱和拆箱[C#] 栈和托管堆 值类型和引用类型/强制类型转换 装箱和拆箱 值类型和引用类型 强制类型转换/装箱和拆箱

一, 栈和托管堆

通用类型系统(CTS)区分两种基本类型:值类型和引用类型.它们之间的根本区别在于 它们在内存中的存储方式..NET 使用两种不同的物理内存块来存储数据—栈和托管堆.如 下图所示:

值类型总是在内存中占用一个预定义的字节数(例如,int 类型占 4 个字节,而 string 类 型占用的字节数会根据字符串的长度不同而不同),当声明一个值类型变量时,会在栈中分 配适当大小的内存(除了引用类型的值类型成员外,如类的 int 字段),内存中的这个空间用 来存储变量所含的值..NET 维护一个栈指针,它包含栈中下一个可用内存空间的地址.当 一个变量离开作用域时, 栈指针向下移动被释放变量所占用的字节数, 所以它仍指向下一个 可用地址. 引用变量也利用栈,但这时栈包含的只是对另一个内存位置的引用,而不是实际值.这 个位置是托管堆中的一个地址.和栈一样,它也维护一个指针,包含堆中下一个可用内存空 间的地址.但是,堆不是先入后出的,因为对对象的引用可在我们的程序中传递(例如,作 为参数传递给方法调用),堆中的对象不会在程序的一个预定点离开作用域.为了在不使用

在堆中分配的内存时将它释放,.NET 定期执行垃圾收集.垃圾收集器递归地检查应用程序 中所有的对象引用. 引用不再有效的对象使用的内存无法从程序中访问, 该内存就可以回收.

二,类型层次结构

CTS 定义了一种类型层次结构,该结构不仅描述了不同的预定义类型,还指出用户定义 类型在层次结构中的位置.

三,引用类型

引用类型包含一个指针,指向堆中存储对象本身的位置.因为引用类型只包含引用,不 包含实际的值,对方法体内参数所做的任何修改都将影响传递给方法调用的引用类型的变 量.

下图显示了声明一个字符串变量并把它作为参数传递给一个方法时所发生的事情.

当声明字符串变量 s1 时,一个值被压入栈中,它指向栈中的一个位置.在上图中,引 用存放在地址 1243044 中,而实际的字符串存放在堆的地址 12262032 中.当该字符串 传递给一个方法时,在栈上对应输入参数声明了一个新的变量(这次是在地址 1243032 上),保存在引用变量,即堆中内存位置中的值被传递给这个新的变量.

委托是引用方法的一种引用类型,类似于 C++中的函数指针(两者的主要区别在于委托 包括调用其方法的对象).

四,预定义的引用类型

有两种引用类型在 C#中受到了特别的重视, 它们的 C#别名和预定义值类型的 C#别名很 相像.第一种是 Object 类(C#别名是 object, o 小写).这是所有值类型和引用类型的最 终基类.因为所有的类型派生自 Object,所以可以把任何类型转换为 Object 类型,甚至 值类型也可以转换.这个把值类型转换为 Object 的过程称为装箱 装箱.所有的值类型都派生自 装箱 引用类型,在这件看似矛盾的事情背后,装箱的作用不可或缺.

第二种是 String 类.字符串代表一个固定不变的 Unicode 字符序列.这种不变性意味 着,一旦在堆中分配了一个字符串,它的值将永远不会改变.如果值改变了,.NET 就创建

一个全新的 String 对象,并把它赋值给该变量.这意味着,字符串在很多方面都像值类型, 而不像引用类型.如果把一个字符串传递给方法,然后在方法体内改变参数的值,这不会影 响最初的字符串(当然,除非参数是按引用传递的).C#提供了别名 string(s 小写)来代表 System.String 类. 如果在代码中使用 String, 必须在代码一开始添加 using System; 这 一行.使用内建的别名 string 则不需要添加 using System;

五,强制类型转换

long x=12345; int k=(int) x; //发生收缩型强制类型转换 从较小数据类型到较大数据类型的转换称为扩展转换,否则称为收缩转换.编译器能进 行隐式的扩展转换, 对于收缩转换必须进行显式的强制性转换. 因为收缩转换会导致丢失数 据,在转换前我们要检查实际值是否超出目标类型的范围.另一个办法是使用 checked 运 算符,如果转换时丢失数据将抛出一个错误.

强制类型转换即可针对值类型,又可针对引用类型.

六,装箱和拆箱(boxing/unboxing)

值类型和引用类型都是从 Object 类派生的.这意味着任何一个以对象为参数的方法,都可 以给它传递一个值类型.相似地,值类型可以调用一个 Object 类方法: int j=4; string str=j.ToString();

这里是另一个强制类型转换的例子.您可能还记得,一个值类型变量包含存储在栈中的
数据.您也许不明白值类型的变量如何调用一个引用类型的方法.答案是在一个称为装箱 (boxing)的过程中,值类型变量被隐式转换为引用类型.从概念上来讲,装箱的过程就是 对应值类型创建一个临时的引用类型的"箱子".下面是 IL 代码: IL_000: ldc.i4.4 IL_001: stloc.0 IL_002: ldloca.s V_0 //Load the int 4 onto the stack //Pop the value off the stack and into V_0 //Push the address of variable V_0 onto the stack

//Call Int32::ToString() IL_004: call instance string[mscorlib]System.Int32::ToString() V_0,它加载指向 V_0 变量的一个托管指针.ToString()方

关键的语句是 ldloca.s

法是在这个托管指针上调用,而不是在值本身调用. 还可以以下面正常的转换语法显式地将一个值装箱: int j=4; object ojb=(object) j;

使用相同的类型转换语法可以把装箱的变量转换回值类型: int k=(int)obj;

对拆箱操作有一些限制.只能将显式装箱的变量进行拆箱.正常的强制转换中的限制在

这里也适用.例如,如果把一个 long 型值装箱为一个对象,我们不能把该对象拆箱为一个 int 型值,虽然在拆箱后可以显式地把 long 转换为 int: long x=1000; object obj=(object) x; int i=(int)((long)obj);

装箱与拆箱示意图: 装箱与拆箱示意图: 拆箱示意图

摘自<<C#程序员参考手册>>

二:方 法
覆盖与重载(override/overload) [C#]

1. 方法签名与方法的显式隐藏
以下程序中,子类 B 与父类 A 存在签名相同的函数,将产生方法隐藏.由于没有显式使 用 new 修饰符,编译时出现警告. 签名相同简单的讲是指忽略访问控制符,函数返回值,参数名后其它内容相同. 如:internal int Print(int x) public void Print(int y) protected float Print(int z) 忽略访问控制符,返回值与参数名后都变成了 Print(int),所以它们都是签名相同的函 数. public int Print(int x, int y) 和 public int Print(int x) 属于不同签名的函数 public int Print(int X) 和 public int Print(float x) 属于不同签名的函数 当 new 关键字用作修饰符时,可以在派生类中隐藏基类的方法,也就说在派生类的方 法是 new 关键字新定义出来的方法,而不是基类的方法.在不使用 New 关键字来隐藏基 类方法也是可以的,编译器会出现一个警告,提示如果有意隐藏基类的方法,请使用 New 关键字修饰. 这种方法不应该过多地使用,因为它破坏了类型之间良好的继承关系,容易造成理解和 维护上的困难.

using System; using System.Collections.Generic; using System.Text;

namespace LearnCSharp {

class A { internal void Print(int x) { Console.WriteLine("A: {0}", x); } }

class B : A { //当 A 中的 Print()成员用 public,protected,internal 修饰,B 从 A 继承了 Print() 方法时,会出现如下警告: //"LearnCSharp.B.Print()"隐藏了继承的成员"LearnCSharp.A.Print()",如果是有 意隐藏,请使用关键字 new public void Print(int y) { Console.WriteLine("B: {0}", y); } }

class P { public static void Main(string[] args) { A a = new A(); a.Print(3); Console.ReadKey();

B b = new B(); b.Print(4);

Console.ReadKey(); } }

}

输出: A: 3 B: 4

用关键字 new,"LearnCSharp.B.Print()"显式隐藏了继承的成员 "LearnCSharp.A.Print()":

using System; using System.Collections.Generic; using System.Text;

namespace LearnCSharp { class A { internal void Print(int x) { Console.WriteLine("A: {0}", x); } }

class B : A { public new void Print(int y)

{ Console.WriteLine("B: {0}", y); } }

class P { public static void Main(string[] args) { A a = new A(); a.Print(3); Console.ReadKey();

B b = new B(); b.Print(4); Console.ReadKey(); } }

}

输出: A: 3 B: 4

把基类中的 Print()方法变成 virtual 方法后:

using System; using System.Collections.Generic; using System.Text;

namespace LearnCSharp { class A { internal virtual void Print(int x) { Console.WriteLine("A: {0}", x); } }

class B : A { //警告:"LearnCSharp.B.Print()"将隐藏继承的成员"LearnCSharp.A.Print()".若 要使当前成员重写该实现, //请添加关键字 override,否则添加关键字 new. public void Print(int y) { Console.WriteLine("B: {0}", y); } }

class P { public static void Main(string[] args) { A a = new A(); a.Print(3); Console.ReadKey();

B b = new B(); b.Print(4); Console.ReadKey(); } }

}

输出: A: 3 B: 4

添加 override 关键字后:

using System; using System.Collections.Generic; using System.Text;

namespace LearnCSharp { class A { public virtual void Print(int x) { Console.WriteLine("A: {0}", x); } }

class B : A {

public override void Print(int y) { Console.WriteLine("B: {0}", y); } }

class P { public static void Main(string[] args) { A a = new A(); a.Print(3); Console.ReadKey();

B b = new B(); b.Print(4); Console.ReadKey(); } }

}

输出: A: 3 B: 4

添加关键字 abstract 将类 A 变成抽象类后,无法创建抽象类或接口"LearnCSharp.A"的 实例.修改后为:

using System; using System.Collections.Generic; using System.Text;

namespace LearnCSharp { abstract class A { public virtual void Print(int x) { Console.WriteLine("A: {0}", x); } }

class B : A { public override void Print(int y) { Console.WriteLine("B: {0}", y); } }

class P { public static void Main(string[] args) { B b = new B(); b.Print(4); Console.ReadKey(); }

}

}

输出: B: 4

以下程序展示了 new 和 override 的本质区别:

using System; using System.Collections.Generic; using System.Text;

namespace LearnCSharp { abstract class A { public virtual void Print() { Console.WriteLine("这是虚方法"); } }

class B1 : A { public override void Print() { Console.WriteLine("这是新的方法"); } }

class B2 : A { public new void Print() { Console.WriteLine("这是另一个新的方法"); } }

class P { public static void Main(string[] args) { A a1 = new B1(); A a2 = new B2(); a1.Print(); a2.Print(); Console.ReadKey(); } }

}

输出: 这是新的方法 这是虚方法

总结: New 关键字主要用来区别派生类和基类同名方法的选择问题, 通过隐藏基类方法, 达到使编译器调用正确的方法的目的. Override 主要用来对基类的方法和虚方法进行重写.

2. 方法重载与重写(overload & override)

override 表示"重写",用于继承一个基类的时候,基类当中虚拟成员的实现.一般语境里, 如果说这个 method(方法)是被 override 来的,就是说在定义这个方法的类的父类中有 一个与这个方法同名且参数类型列表相同的方法,在子类中,这个方法被 override 了.在 对这个子类的实例调用该方法时,编译器确切的知道调用的是这个子类的方法.override 指它随时随地都只有一种含义. overload 表示"重载",用于同一类中同名方法但参数个数或类型不同的实现,也就是让 方法有不同签名的版本.一般语境里 overload 是对 method(方法)而言的,可以指一个 类中多个名字相同而参数类型列表不相同的方法, 这个名字代表的方法就是被 overload 了 的.编译器会根据参数类型列表的不同来决定调用叫这个名字的很多方法中具体的哪一个. 指同样的东西在不同的地方具有多种含义.

posted on 2008-02-29 00:25 冷风 阅读(14) 评论(1) 编辑 收藏

发表评论

回复 引用 查看

2008-02-29 17:21 | JefferyX

1. 覆盖与重载(override/overload) [C#] 2. 索引器(indexer) [C#] [引用提示]JefferyX 引用了该文章, 地址: http://www.cnblogs.com/JCSU/articles/1086412.html

抽象方法和虚方法的区别 [C#]
1.(abstract)抽象方法和(virtualt)虚方法的区别在于:虚方法有一个实现部分可以被子 类继承,从而使子类获得和基类相同的方法,另外也为派生类提供了覆盖该方法的选项.相 反, 抽象方法没有提供实现部分, 是一种强制派生类覆盖的方法 (否则派生类不能成具体类)

2.(abstract)抽象方法只能在抽象类中声明,(virtual)虚方法不是.

3.(abstract)抽象方法必须在派生类中重写而(virtual)虚方法不必.

4.(abstract)抽象方法不能声明方法实体,虚方法可以. 包含抽象方法(只有抽象类才可以包含抽象方法)的类不能实例化(也就是说只可以使 用 predected 和 private 修饰符),虚方法可以.

深入剖析 C#的多态 [C#]

一,什么是多态

面向对象程序设计中的另外一个重要概念是多态性.在运行时,可以通过指向基类的指针, 来调用实现派生类中的方法.可以把一组对象放到一个数组中,然后调用它们的方法,在这 种场合下,多态性作用就体现出来了,这些对象不必是相同类型的对象.当然,如果它们都 继承自某个类,你可以把这些派生类,都放到一个数组中.如果这些对象都有同名方法,就 可以调用每个对象的同名方法.

同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性.多 态性通过派生类重载基类中的虚函数型方法来实现.

在面向对象的系统中,多态性是一个非常重要的概念,它允许客户对一个对象进行操作,由 对象来完成一系列的动作,具体实现哪个动作,如何实现由系统负责解释.

"多态性"一词最早用于生物学,指同一种族的生物体具有相同的特性.在 C#中,多态性的 定义是:同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执 行结果.C#支持两种类型的多态性:

● 编译时的多态性 编译时的多态性是通过重载来实现的.对于非虚的成员来说,系统在编译时,根据传递的参 数,返回的类型等信息决定实现何种操作.

● 运行时的多态性 运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作.C#中,运行 时的多态性通过虚成员实现.

编译时的多态性为我们提供了运行速度快的特点, 而运行时的多态性则带来了高度灵活和抽 象的特点.

二,实现多态

多态性是类为方法(这些方法以相同的名称调用)提供不同实现方式的能力.多态性允许对 类的某个方法进行调用而无需考虑该方法所提供的特定实现.例如,可能有名为 Road 的 类,它调用另一个类的 Drive 方法.这另一个类 Car 可能是 SportsCar 或 SmallCar, 但二者都提供 Drive 方法.虽然 Drive 方法的实现因类的不同而异,但 Road 类仍可以 调用它,并且它提供的结果可由 Road 类使用和解释. 可以用不同的方式实现组件中的多态性:

● 接口多态性. ● 继承多态性. ● 通过抽象类实现的多态性.

接口多态性
多个类可实现相同的"接口",而单个类可以实现一个或多个接口.接口本质上是类需要如何 响应的定义.接口描述类需要实现的方法,属性和事件,以及每个成员需要接收和返回的参 数类型,但将这些成员的特定实现留给实现类去完成.

组件编程中的一项强大技术是能够在一个对象上实现多个接口. 每个接口由一小部分紧密联 系的方法,属性和事件组成.通过实现接口,组件可以为要求该接口的任何其他组件提供功 能, 而无需考虑其中所包含的特定功能. 这使后续组件的版本得以包含不同的功能而不会干 扰核心功能.其他开发人员最常使用的组件功能自然是组件类本身的成员.然而,包含大量 成员的组件使用起来可能比较困难. 可以考虑将组件的某些功能分解出来, 作为私下实现的 单独接口.

根据接口来定义功能的另一个好处是, 可以通过定义和实现附加接口增量地将功能添加到组 件中.优点包括:

1.简化了设计过程,因为组件开始时可以很小,具有最小功能;之后,组件继续提供最小功 能,同时不断插入其他的功能,并通过实际使用那些功能来确定合适的功能.

2.简化了兼容性的维护, 因为组件的新版本可以在添加新接口的同时继续提供现有接口. 客 户端应用程序的后续版本可以利用这些接口的优点.

通过继承实现的多态性
多个类可以从单个基类"继承". 通过继承, 类在基类所在的同一实现中接收基类的所有方法, 属性和事件. 这样, 便可根据需要来实现附加成员, 而且可以重写基成员以提供不同的实现. 请注意,继承类也可以实现接口,这两种技术不是互斥的.

C# 通过继承提供多态性.对于小规模开发任务而言,这是一个功能强大的机制,但对于大 规模系统, 通常证明会存在问题. 过分强调继承驱动的多态性一般会导致资源大规模地从编 码转移到设计,这对于缩短总的开发时间没有任何帮助.

何时使用继承驱动的多态性呢?使用继承首先是为了向现有基类添加功能. 若从经过完全调 试的基类框架开始, 则程序员的工作效率将大大提高, 方法可以增量地添加到基类而不中断 版本.当应用程序设计包含多个相关类,而对于某些通用函数,这些相关类必须共享同样的 实现时,您也可能希望使用继承.重叠功能可以在基类中实现,应用程序中使用的类可以从 该基类中派生.抽象类合并继承和实现的功能,这在需要二者之一的元素时可能很有用.

通过抽象类实现的多态性

抽象类同时提供继承和接口的元素.抽象类本身不能实例化,它必须被继承.该类的部分或 全部成员可能未实现,该实现由继承类提供.已实现的成员仍可被重写,并且继承类仍可以 实现附加接口或其他功能.

抽象类提供继承和接口实现的功能.抽象类不能示例化,必须在继承类中实现.它可以包含 已实现的方法和属性,但也可以包含未实现的过程,这些未实现过程必须在继承类中实现. 这使您得以在类的某些方法中提供不变级功能, 同时为其他过程保持灵活性选项打开. 抽象 类的另一个好处是:当要求组件的新版本时,可根据需要将附加方法添加到基类,但接口必 须保持不变.

何时使用抽象类呢?当需要一组相关组件来包含一组具有相同功能的方法, 但同时要求在其 他方法实现中具有灵活性时,可以使用抽象类.当预料可能出现版本问题时,抽象类也具有 价值,因为基类比较灵活并易于被修改.

三:委托与事件

委托和事件 [C#]

引 言
委托 和 事件在 .Net Framework 中的应用非常广泛,然而,较好地理解委托和事件对很 多接触 C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真是 太容易了,而没有过去的人每次见到委托和事件就觉得心里别(biè)得慌,混身不自在. 本文中,我将通过两个范例由浅入深地讲述什么是委托,为什么要使用委托,事件的由 来,.Net Framework 中的委托和事件,委托和事件对 Observer 设计模式的意义,对它 们的中间代码也做了讨论.

将方法作为方法的参数 将方法作为方法的参数
我们先不管这个标题如何的绕口, 也不管委托究竟是个什么东西, 来看下面这两个最简单的 方法,它们不过是在屏幕上输出一句问候的话语:

public void GreetPeople(string name) { // 做某些额外的事情,比如初始化之类,此处略 EnglishGreeting(name); } public void EnglishGreeting(string name) { Console.WriteLine("Morning, " + name); } 暂且不管这两个方法有没有什么实际意义.GreetPeople 用于向某人问好,当我们传递代 表某人姓名的 name 参数,比如说"Jimmy",进去的时候,在这个方法中,将调用 EnglishGreeting 方法,再次传递 name 参数,EnglishGreeting 则用于向屏幕输出 "Morning, Jimmy".

现在假设这个程序需要进行全球化,哎呀,不好了,我是中国人,我不明白"Morning"是什 么意思,怎么办呢?好吧,我们再加个中文版的问候方法:

public void ChineseGreeting(string name){ Console.WriteLine("早上好, " + name); } 这时候,GreetPeople 也需要改一改了,不然如何判断到底用哪个版本的 Greeting 问候 方法合适呢?在进行这个之前,我们最好再定义一个枚举作为判断的依据:

public enum Language{ English, Chinese }

public void GreetPeople(string name, Language lang){ //做某些额外的事情,比如初始化之类,此处略 swith(lang){ case Language.English: EnglishGreeting(name); break; case Language.Chinese: ChineseGreeting(name); break; } }

OK,尽管这样解决了问题,但我不说大家也很容易想到,这个解决方案的可扩展性很差, 如果日后我们需要再添加韩文版,日文版,就不得不反复修改枚举和 GreetPeople()方法, 以适应新的需求.

在考虑新的解决方案之前,我们先看看 GreetPeople 的方法签名:

public void GreetPeople(string name, Language lang) 我们仅看 string name,在这里,string 是参数类型,name 是参数变量,当我们赋给 name 字符串"jimmy"时,它就代表"jimmy"这个值;当我们赋给它"张子阳"时,它又代 表着"张子阳"这个值.然后,我们可以在方法体内对这个 name 进行其他操作.哎,这简 直是废话么,刚学程序就知道了.

如果你再仔细想想,假如 GreetPeople()方法可以接受一个参数变量,这个变量可以代表 另一个方法,当我们给这个变量赋值 EnglishGreeting 的时候,它代表着 EnglsihGreeting() 这个方法;当我们给它赋值 ChineseGreeting 的时候,它又代表着 ChineseGreeting()方法.我们将这个参数变量命名为 MakeGreeting,那么不是可以如 同给 name 赋值时一样,在调用 GreetPeople()方法的时候,给这个 MakeGreeting 参 数也赋上值么(ChineseGreeting 或者 EnglsihGreeting 等)?然后,我们在方法体内,也 可以像使用别的参数一样使用 MakeGreeting.但是,由于 MakeGreeting 代表着一个方 法,它的使用方式应该和它被赋的方法(比如 ChineseGreeting)是一样的,比如:

MakeGreeting(name); 好了,有了思路了,我们现在就来改改 GreetPeople()方法,那么它应该是这个样子了:

public void GreetPeople(string name, *** MakeGreeting){ MakeGreeting(name); } 注意到 *** ,这个位置通常放置的应该是参数的类型,但到目前为止,我们仅仅是想到应 该有个可以代表方法的参数,并按这个思路去改写 GreetPeople 方法,现在就出现了一个 大问题:这个代表着方法的 MakeGreeting 参数应该是什么类型的?NOTE:这里已不再 : 需要枚举了,因为在给 MakeGreeting 赋值的时候动态地决定使用哪个方法,是 ChineseGreeting 还是 EnglishGreeting,而在这个两个方法内部,已经对使用 "morning"还是"早上好"作了区分.

聪明的你应该已经想到了,现在是委托该出场的时候了,但讲述委托之前,我们再看看 MakeGreeting 参数所能代表的 ChineseGreeting()和 EnglishGreeting()方法的签名:

public void EnglishGreeting(string name) public void ChineseGreeting(string name) 如同 name 可以接受 String 类型的"true"和"1",但不能接受 bool 类型的 true 和 int 类 型的 1 一样. MakeGreeting 的 参数类型定义 应该能够确定 MakeGreeting 可以代表的 方法种类,再进一步讲,就是 MakeGreeting 可以代表的方法 的 参数类型和返回类型. 于是,委托出现了:它定义了 MakeGreeting 参数所能代表的方法的种类,也就是 MakeGreeting 参数的类型. NOTE:如果上面这句话比较绕口,我把它翻译成这样:string 定义了 name 参数所能代 : 表的值的种类,也就是 name 参数的类型. 本例中委托的定义:

public delegate void GreetingDelegate(string name); 可以与上面 EnglishGreeting()方法的签名对比一下,除了加入了 delegate 关键字以外, 其余的是不是完全一样?

现在,让我们再次改动 GreetPeople()方法,如下所示:

public void GreetPeople(string name, GreetingDelegate MakeGreeting){ MakeGreeting(name); } 如你所见,委托 GreetingDelegate 出现的位置与 string 相同,string 是一个类型,那么 GreetingDelegate 应该也是一个类型,或者叫类(Class).但是委托的声明方式和类却完 全不同,这是怎么一回事?实际上,委托在编译的时候确实会编译成类.因为 Delegate 是 一个类,所以在任何可以声明类的地方都可以声明委托.更多的内容将在下面讲述,现在, 请看看这个范例的完整代码:

using System; using System.Collections.Generic; using System.Text;

namespace Delegate { //定义委托,它定义了可以代表的方法的类型 public delegate void GreetingDelegate(string name); class Program {

private static void EnglishGreeting(string name) { Console.WriteLine("Morning, " + name); }

private static void ChineseGreeting(string name) { Console.WriteLine("早上好, " + name); }

//注意此方法,它接受一个 GreetingDelegate 类型的方法作为参数 private static void GreetPeople(string name, GreetingDelegate MakeGreeting) { MakeGreeting(name); }

static void Main(string[] args) { GreetPeople("Jimmy Zhang", EnglishGreeting); GreetPeople("张子阳", ChineseGreeting); Console.ReadKey(); } } }

输出如下: Morning, Jimmy Zhang 早上好, 张子阳

我们现在对委托做一个总结:

委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递, 这种将方法动态地赋给参数的做法,可以避免在程序中大量使用 If-Else(Switch)语句,同 时使得程序具有更好的可扩展性.

将方法绑定到委托
看到这里,是不是有那么点如梦初醒的感觉?于是,你是不是在想:在上面的例子中,我不 一定要直接在 GreetPeople()方法中给 name 参数赋值,我可以像这样使用变量:

static void Main(string[] args) { string name1, name2; name1 = "Jimmy Zhang"; name2 = "张子阳";

GreetPeople(name1, EnglishGreeting); GreetPeople(name2, ChineseGreeting); Console.ReadKey(); } 而既然委托 GreetingDelegate 和 类型 string 的地位一样,都是定义了一种参数类型, 那么,我是不是也可以这么使用委托?

static void Main(string[] args) { GreetingDelegate delegate1, delegate2; delegate1 = EnglishGreeting; delegate2 = ChineseGreeting;

GreetPeople("Jimmy Zhang", delegate1); GreetPeople("张子阳", delegate2); Console.ReadKey(); } 如你所料,这样是没有问题的,程序一如预料的那样输出.这里,我想说的是委托不同于 string 的一个特性:可以将多个方法赋给同一个委托,或者叫将多个方法绑定到同一个委 托,当调用这个委托的时候,将依次调用其所绑定的方法.在这个例子中,语法如下:

static void Main(string[] args) { GreetingDelegate delegate1; delegate1 = EnglishGreeting; // 先给委托类型的变量赋值 delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法

// 将先后调用 EnglishGreeting 与 ChineseGreeting 方法 GreetPeople("Jimmy Zhang", delegate1); Console.ReadKey(); }

输出为: Morning, Jimmy Zhang 早上好, Jimmy Zhang 实际上,我们可以也可以绕过 GreetPeople 方法,通过委托来直接调用 EnglishGreeting 和 ChineseGreeting:

static void Main(string[] args) { GreetingDelegate delegate1; delegate1 = EnglishGreeting; // 先给委托类型的变量赋值 delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法

// 将先后调用 EnglishGreeting 与 ChineseGreeting 方法 delegate1 ("Jimmy Zhang"); Console.ReadKey(); } NOTE:这在本例中是没有问题的,但回头看下上面 GreetPeople()的定义,在它之中可 : 以做一些对于 EnglshihGreeting 和 ChineseGreeting 来说都需要进行的工作, 为了简便 我做了省略.

注意这里,第一次用的"=",是赋值的语法;第二次,用的是"+=",是绑定的语法.如果 第一次就使用"+=",将出现"使用了未赋值的局部变量"的编译错误.

我们也可以使用下面的代码来这样简化这一过程:

GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting); delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法

看到这里,应该注意到,这段代码第一条语句与实例化一个类是何其的相似,你不禁想到: 上面第一次绑定委托时不可以使用"+="的编译错误,或许可以用这样的方法来避免:

GreetingDelegate delegate1 = new GreetingDelegate(); delegate1 += EnglishGreeting; delegate1 += ChineseGreeting; // 这次用的是 "+=",绑定语法. // 给此委托变量再绑定一个方法

但实际上,这样会出现编译错误: "GreetingDelegate"方法没有采用"0"个参数的重载. 尽管这样的结果让我们觉得有点沮丧,但是编译的提示:"没有 0 个参数的重载"再次让我 们联想到了类的构造函数.我知道你一定按捺不住想探个究竟,但再此之前,我们需要先把 基础知识和应用介绍完.

既然给委托可以绑定一个方法,那么也应该有办法取消对方法的绑定,很容易想到,这个语 法是"-=":

static void Main(string[] args) { GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting); delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法

// 将先后调用 EnglishGreeting 与 ChineseGreeting 方法 GreetPeople("Jimmy Zhang", delegate1); Console.WriteLine();

delegate1 -= EnglishGreeting; //取消对 EnglishGreeting 方法的绑定 // 将仅调用 ChineseGreeting GreetPeople("张子阳", delegate1); Console.ReadKey(); } 输出为: Morning, Jimmy Zhang 早上好, Jimmy Zhang 早上好, 张子阳

让我们再次对委托作个总结:

使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用"调用"这个词, 是因为此变量代表一个方法),可以依次调用所有绑定的方法.

事件的由来
我们继续思考上面的程序:上面的三个方法都定义在 Programe 类中,这样做是为了理解 的方便,实际应用中,通常都是 GreetPeople 在一个类中,ChineseGreeting 和 EnglishGreeting 在另外的类中.现在你已经对委托有了初步了解,是时候对上面的例子 做个改进了.假设我们将 GreetingPeople()放在一个叫 GreetingManager 的类中,那么 新程序应该是这个样子的:

namespace Delegate { //定义委托,它定义了可以代表的方法的类型 public delegate void GreetingDelegate(string name);

//新建的 GreetingManager 类 public class GreetingManager{ public void GreetPeople(string name, GreetingDelegate MakeGreeting) { MakeGreeting(name); } }

class Program { private static void EnglishGreeting(string name) { Console.WriteLine("Morning, " + name); }

private static void ChineseGreeting(string name) { Console.WriteLine("早上好, " + name); }

static void Main(string[] args) { // ... ... } } } 这个时候,如果要实现前面演示的输出效果,Main 方法我想应该是这样的:

static void Main(string[] args) { GreetingManager gm = new GreetingManager();

gm.GreetPeople("Jimmy Zhang", EnglishGreeting); gm.GreetPeople("张子阳", ChineseGreeting); } 我们运行这段代码,嗯,没有任何问题.程序一如预料地那样输出了:

Morning, Jimmy Zhang 早上好, 张子阳

现在,假设我们需要使用上一节学到的知识,将多个方法绑定到同一个委托变量,该如何做 呢?让我们再次改写代码:

static void Main(string[] args) { GreetingManager gm = new GreetingManager(); GreetingDelegate delegate1; delegate1 = EnglishGreeting; delegate1 += ChineseGreeting;

gm.GreetPeople("Jimmy Zhang", delegate1); }

输出: Morning, Jimmy Zhang 早上好, Jimmy Zhang 到了这里,我们不禁想到:面向对象设计,讲究的是对象的封装,既然可以声明委托类型的 变量(在上例中是 delegate1), 我们何不将这个变量封装到 GreetManager 类中?在这个 类的客户端中使用不是更方便么?于是,我们改写 GreetManager 类,像这样:

public class GreetingManager{ //在 GreetingManager 类的内部声明 delegate1 变量

public GreetingDelegate delegate1;

public void GreetPeople(string name, GreetingDelegate MakeGreeting) { MakeGreeting(name); } } 现在,我们可以这样使用这个委托变量:

static void Main(string[] args) { GreetingManager gm = new GreetingManager(); gm.delegate1 = EnglishGreeting; gm.delegate1 += ChineseGreeting;

gm.GreetPeople("Jimmy Zhang", gm.delegate1); }

输出为: Morning, Jimmy Zhang 早上好, Jimmy Zhang 尽管这样做没有任何问题,但我们发现这条语句很奇怪.在调用 gm.GreetPeople 方法的 时候,再次传递了 gm 的 delegate1 字段:

gm.GreetPeople("Jimmy Zhang", gm.delegate1); 既然如此,我们何不修改 GreetingManager 类成这样:

public class GreetingManager{ //在 GreetingManager 类的内部声明 delegate1 变量 public GreetingDelegate delegate1;

public void GreetPeople(string name) { if(delegate1!=null){ delegate1(name); } } } 在客户端,调用看上去更简洁一些: //如果有方法注册委托变量 //通过委托调用方法

static void Main(string[] args) { GreetingManager gm = new GreetingManager(); gm.delegate1 = EnglishGreeting; gm.delegate1 += ChineseGreeting;

gm.GreetPeople("Jimmy Zhang"); 量 }

//注意,这次不需要再传递 delegate1 变

输出为: Morning, Jimmy Zhang 早上好, Jimmy Zhang 尽管这样达到了我们要的效果,但是还是存在着问题:

在这里,delegate1 和我们平时用的 string 类型的变量没有什么分别,而我们知道,并不 是所有的字段都应该声明成 public, 合适的做法是应该 public 的时候 public, 应该 private 的时候 private. 我们先看看如果把 delegate1 声明为 private 会怎样?结果就是:这简直就是在搞笑. 因为声明委托的目的就是为了把它暴露在类的客户端进行方法的注册,你把它声明为 private 了,客户端对它根本就不可见,那它还有什么用?再看看把 delegate1 声明为 public 会怎样?结果就是:在客户端可以对它进行随意的赋值等操作,严重破坏对象的封

装性.

最后,第一个方法注册用"=",是赋值语法,因为要进行实例化,第二个方法注册则用的是 "+=".但是,不管是赋值还是注册,都是将方法绑定到委托上,除了调用时先后顺序不同, 再没有任何的分别,这样不是让人觉得很别扭么?

现在我们想想,如果 delegate1 不是一个委托类型,而是一个 string 类型,你会怎么做? 答案是使用属性对字段进行封装.

于是, Event 出场了, 它封装了委托类型的变量, 使得: 在类的内部, 不管你声明它是 public 还是 protected,它总是 private 的.在类的外部,注册"+="和注销"-="的访问限定符与 你在声明事件时使用的访问符相同.

我们改写 GreetingManager 类,它变成了这个样子:

public class GreetingManager{ //这一次我们在这里声明一个事件 public event GreetingDelegate MakeGreet;

public void GreetPeople(string name) { MakeGreet(name); } } 很容易注意到:MakeGreet 事件的声明与之前委托变量 delegate1 的声明唯一的区别是 多了一个 event 关键字.看到这里,在结合上面的讲解,你应该明白到:事件其实没什么 不好理解的,声明一个事件不过类似于声明一个进行了封装的委托类型的变量而已.

为了证明上面的推论,如果我们像下面这样改写 Main 方法:

static void Main(string[] args) { GreetingManager gm = new GreetingManager(); gm.MakeGreet = EnglishGreeting; gm.MakeGreet += ChineseGreeting; // 编译错误 1

gm.GreetPeople("Jimmy Zhang"); } 会得到编译错误:事件"Delegate.GreetingManager.MakeGreet"只能出现在 += 或 -= 的左边(从类型"Delegate.GreetingManager"中使用时除外).

事件和委托的编译代码
这时候,我们注释掉编译错误的行,然后重新进行编译,再借助 Reflactor 来对 event 的 声明语句做一探究,看看为什么会发生这样的错误:

public event GreetingDelegate MakeGreet;

可以看到, 实际上尽管我们在 GreetingManager 里将 MakeGreet 声明为 public, 但是, 实际上 MakeGreet 会被编译成 私有字段,难怪会发生上面的编译错误了,因为它根本就 不允许在 GreetingManager 类的外面以赋值的方式访问,从而验证了我们上面所做的推 论.

我们再进一步看下 MakeGreet 所产生的代码: private GreetingDelegate MakeGreet; //对事件的声明 实际是 声明一个私有的委托 变量

[MethodImpl(MethodImplOptions.Synchronized)]

public void add_MakeGreet(GreetingDelegate value){ this.MakeGreet = (GreetingDelegate) Delegate.Combine(this.MakeGreet, value); }

[MethodImpl(MethodImplOptions.Synchronized)] public void remove_MakeGreet(GreetingDelegate value){ this.MakeGreet = (GreetingDelegate) Delegate.Remove(this.MakeGreet, value); } 现在已经很明确了:MakeGreet 事件确实是一个 GreetingDelegate 类型的委托, 只不过 不管是不是声明为 public,它总是被声明为 private.另外,它还有两个方法,分别是 add_MakeGreet 和 remove_MakeGreet,这两个方法分别用于注册委托类型的方法和 取消注册.实际上也就是: "+= "对应 add_MakeGreet,"-="对应 remove_MakeGreet.而这两个方法的访问限制取决于声明事件时的访问限制符. 在 add_MakeGreet()方法内部,实际上调用了 System.Delegate 的 Combine()静态方 法,这个方法用于将当前的变量添加到委托链表中.我们前面提到过两次,说委托实际上是 一个类,在我们定义委托的时候:

public delegate void GreetingDelegate(string name); 当编译器遇到这段代码的时候,会生成下面这样一个完整的类:

public class GreetingDelegate:System.MulticastDelegate{ public GreetingDelegate(object @object, IntPtr method); public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object); public virtual void EndInvoke(IAsyncResult result); public virtual void Invoke(string name); }

关于这个类的更深入内容,可以参阅《CLR Via C#》等相关书籍,这里就不再讨论了.

委托, 委托,事件与 Observer 设计模式 范例说明
上面的例子已不足以再进行下面的讲解了, 我们来看一个新的范例, 因为之前已经介绍了很 多的内容,所以本节的进度会稍微快一些:

假设我们有个高档的热水器,我们给它通上电,当水温超过 95 度的时候:1,扬声器会开 始发出语音,告诉你水的温度;2,液晶屏也会改变水温的显示,来提示水已经快烧开了.

现在我们需要写个程序来模拟这个烧水的过程, 我们将定义一个类来代表热水器, 我们管它 叫:Heater,它有代表水温的字段,叫做 temperature;当然,还有必不可少的给水加热 方法 BoilWater(),一个发出语音警报的方法 MakeAlert(),一个显示水温的方法, ShowMsg().

namespace Delegate { class Heater { private int temperature; // 水温 // 烧水 public void BoilWater() { for (int i = 0; i <= 100; i++) { temperature = i;

if (temperature > 95) { MakeAlert(temperature); ShowMsg(temperature); } } }

// 发出语音警报 private void MakeAlert(int param) { Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:" , param); }

// 显示水温 private void ShowMsg(int param) { Console.WriteLine("Display:水快开了,当前温度:{0}度." , param); } }

class Program { static void Main() { Heater ht = new Heater(); ht.BoilWater(); } } }

Observer 设计模式简介
上面的例子显然能完成我们之前描述的工作, 但是却并不够好. 现在假设热水器由三部分组 成:热水器,警报器,显示器,它们来自于不同厂商并进行了组装.那么,应该是热水器 热水器仅 热水器

仅负责烧水,它不能发出警报也不能显示水温;在水烧开时由警报器 警报器发出警报,显示器 显示器显示 警报器 显示器 提示和水温.

这时候,上面的例子就应该变成这个样子:

// 热水器 public class Heater { private int temperature;

// 烧水 private void BoilWater() { for (int i = 0; i <= 100; i++) { temperature = i; } } }

// 警报器 public class Alarm{ private void MakeAlert(int param) { Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:" , param); } }

// 显示器 public class Display{ private void ShowMsg(int param) { Console.WriteLine("Display:水已烧开,当前温度:{0}度." , param); } }

这里就出现了一个问题: 如何在水烧开的时候通知报警器和显示器?在继续进行之前, 我们 先了解一下 Observer 设计模式,Observer 设计模式中主要包括如下两类对象:

1. Subject:监视对象,它往往包含着其他对象所感兴趣的内容.在本范例中, 热水器就是一个监视对象,它包含的其他对象所感兴趣的内容,就是 temprature 字段,当这个字段的值快到 100 时,会不断把数据发给监视它 的对象. 2. Observer:监视者,它监视 Subject,当 Subject 中的某件事发生的时候, 会告知 Observer,而 Observer 则会采取相应的行动.在本范例中, Observer 有警报器和显示器,它们采取的行动分别是发出警报和显示水温. 在本例中,事情发生的顺序应该是这样的:

1. 警报器和显示器告诉热水器,它对它的温度比较感兴趣(注册). 2. 热水器知道后保留对警报器和显示器的引用. 3. 热水器进行烧水这一动作,当水温超过 95 度时,通过对警报器和显示器的引 用,自动调用警报器的 MakeAlert()方法,显示器的 ShowMsg()方法. 类似这样的例子是很多的,GOF 对它进行了抽象,称为 Observer 设计模式:Observer 设计模式是为了定义对象间的一种一对多的依赖关系, 以便于当一个对象的状态改变时, 其 他依赖于它的对象会被自动告知并更新.Observer 模式是一种松耦合的设计模式.

实现范例的 Observer 设计模式

我们之前已经对委托和事件介绍很多了, 现在写代码应该很容易了, 现在在这里直接给出代 码,并在注释中加以说明.

using System; using System.Collections.Generic; using System.Text;

namespace Delegate { // 热水器 public class Heater { private int temperature; public delegate void BoilHandler(int param); public event BoilHandler BoilEvent; //声明委托

//声明事件

// 烧水 public void BoilWater() { for (int i = 0; i <= 100; i++) { temperature = i;

if (temperature > 95) { if (BoilEvent != null) { //如果有对象注册 BoilEvent(temperature); //调用所有注册对象的方法 } } } } }

// 警报器 public class Alarm { public void MakeAlert(int param) { Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:", param); }

}

// 显示器 public class Display { public static void ShowMsg(int param) { //静态方法 Console.WriteLine("Display:水快烧开了,当前温度:{0}度.", param); } }

class Program { static void Main() { Heater heater = new Heater(); Alarm alarm = new Alarm();

heater.BoilEvent += alarm.MakeAlert;

//注册方法 //给匿名对象注册方法

heater.BoilEvent += (new Alarm()).MakeAlert; heater.BoilEvent += Display.ShowMsg;

//注册静态方法

heater.BoilWater(); } } } 输出为:

//烧水,会自动调用注册过对象的方法

Alarm:嘀嘀嘀,水已经 96 度了: Alarm:嘀嘀嘀,水已经 96 度了: Display:水快烧开了,当前温度:96 度. // 省略...

.Net Framework 中的委托与事件
尽管上面的范例很好地完成了我们想要完成的工作,但是我们不仅疑惑:为什么.Net Framework 中的事件模型和上面的不同?为什么有很多的 EventArgs 参数? 在回答上面的问题之前,我们先搞懂 .Net Framework 的编码规范: 委托类型的名称都应该以 EventHandler 结束. 委托的原型定义:有一个 void 返回值,并接受两个输入参数:一个 Object 类型, 一个 EventArgs 类型(或继承自 EventArgs).
o o

o o

事件的命名为 委托去掉 EventHandler 之后剩余的部分. 继承自 EventArgs 的类型应该以 EventArgs 结尾.

再做一下说明:

1. 委托声明原型中的 Object 类型的参数代表了 Subject,也就是监视对象,在 本例中是 Heater(热水器).回调函数(比如 Alarm 的 MakeAlert)可以通过 它访问触发事件的对象(Heater). 2. EventArgs 对象包含了 Observer 所感兴趣的数据,在本例中是 temperature. 上面这些其实不仅仅是为了编码规范而已,这样也使得程序有更大的灵活性.比如说,如果 我们不光想获得热水器的温度, 还想在 Observer 端(警报器或者显示器)方法中获得它的生 产日期,型号,价格,那么委托和方法的声明都会变得很麻烦,而如果我们将热水器的引用 传给警报器的方法,就可以在方法中直接访问热水器了.

现在我们改写之前的范例,让它符合 .Net Framework 的规范:

using System; using System.Collections.Generic; using System.Text;

namespace Delegate {

// 热水器 public class Heater { private int temperature; public string type = "RealFire 001"; public string area = "China Xian"; //声明委托 public delegate void BoiledEventHandler(Object sender, BoiledEventArgs e); public event BoiledEventHandler Boiled; //声明事件 // 添加型号作为演示 // 添加产地作为演示

// 定义 BoiledEventArgs 类,传递给 Observer 所感兴趣的信息 public class BoiledEventArgs : EventArgs { public readonly int temperature; public BoiledEventArgs(int temperature) { this.temperature = temperature; } }

// 可以供继承自 Heater 的类重写,以便继承类拒绝其他对象对它的监视 protected virtual void OnBoiled(BoiledEventArgs e) { if (Boiled != null) { // 如果有对象注册 Boiled(this, e); // 调用所有注册对象的方法 } }

// 烧水. public void BoilWater() { for (int i = 0; i <= 100; i++) { temperature = i; if (temperature > 95) {

//建立 BoiledEventArgs 对象. BoiledEventArgs e = new BoiledEventArgs(temperature); OnBoiled(e); // 调用 OnBolied 方法 } } } }

// 警报器 public class Alarm { public void MakeAlert(Object sender, Heater.BoiledEventArgs e) { Heater heater = (Heater)sender; //访问 sender 中的公共字段 Console.WriteLine("Alarm:{0} - {1}: ", heater.area, heater.type); Console.WriteLine("Alarm: 嘀嘀嘀, 水已经 {0} 度了: e.temperature); ", Console.WriteLine(); } } //这里是不是很熟悉呢?

// 显示器 public class Display { public static void ShowMsg(Object sender, Heater.BoiledEventArgs e) { //静态方法 Heater heater = (Heater)sender; Console.WriteLine("Display:{0} - {1}: ", heater.area, heater.type); Console.WriteLine("Display:水快烧开了,当前温度:{0}度.", e.temperature); Console.WriteLine(); } }

class Program { static void Main() { Heater heater = new Heater(); Alarm alarm = new Alarm();

heater.Boiled += alarm.MakeAlert;

//注册方法 //给匿名对象注册方法

heater.Boiled += (new Alarm()).MakeAlert; heater.Boiled += new Heater.BoiledEventHandler(alarm.MakeAlert); heater.Boiled += Display.ShowMsg;

//也可以这么注册 //注册静态方法

heater.BoilWater(); } } }

//烧水,会自动调用注册过对象的方法

输出为: Alarm:China Xian - RealFire 001: Alarm: 嘀嘀嘀,水已经 96 度了: Alarm:China Xian - RealFire 001: Alarm: 嘀嘀嘀,水已经 96 度了: Alarm:China Xian - RealFire 001: Alarm: 嘀嘀嘀,水已经 96 度了: Display:China Xian - RealFire 001: Display:水快烧开了,当前温度:96 度. // 省略 ...

总结
在本文中我首先通过一个 GreetingPeople 的小程序向大家介绍了委托的概念,委托用来 做什么,随后又引出了事件,接着对委托与事件所产生的中间代码做了粗略的讲述.

在第二个稍微复杂点的热水器的范例中,我向大家简要介绍了 Observer 设计模式,并通 过实现这个范例完成了该模式,随后讲述了.Net Framework 中委托,事件的实现方式. 希望这篇文章能给你带来帮助.

摘自 http://www.cnblogs.com/JimmyZhang/archive/2007/09/23/903360.html

委托模型和事件模型 [C#]

1.多播委托 .

与委托有关的语法: 与委托有关的语法:

定 义 委 托 : <modifiers>

delegate

<return_type>

<delegate_name>

(<argument_list>) public delegate void Message() ; 创 建 委 托 实 例 : <delegate_type> <name> = new <delegate_type> (<method>) Message msg = new Message(Messages.Greeting); 调用委托:<delegate_name> (<argument_list>) msg(); 多播委托是指引用多个方法的委托.当调用委托时,它连续调用每个方法.为了把委托 的单个实例合并为一个多播委托,委托必须是同类型的,返回类型必须是 void,不能带输 出参数选择(但可以带引用参数).多播委托用于 C#的事件模型中.

示例: 示例:多播委托

using System;

public delegate void Message(); //定义 定义一个无参数的委托 定义

public class Messages //Messages 类定义了三个方法打印不同的消息. 返回类型为 void, 不带参数 { public static void Greeting() {

Console.WriteLine("Welcome to Mandolin Co."); }

public static void DateAndTime() { Console.WriteLine(DateTime.Now.ToLongDateString()); }

public static void Maintenance() { Console.WriteLine("System maintenance will be done tonight"); } }

using System;

class MultiDemo { public static void Main() { Message msg; //Create a multi-cast delegate that will print out a number of messages

msg = new Message(Messages.Greeting); //创建 创建委托实例 创建 msg += new Message(Messages.DateAndTime);

Message msg2 = new Message(Messages.Maintenance); //创建 创建委托实例 创建 msg += msg2;//三个 Message 委托串在一起形成了多播委托

msg(); //调用 调用委托 调用

//A delegate is removed from the multi-cast

Console.WriteLine();

msg -= msg2;//从多播委托 msg 中删除了一个委托 msg2 msg(); //调用 调用委托 调用

Console.ReadKey(); } }

运行结果: Welcome to Mandolin Co. 2008 年 3 月 1 日 System maintenance will be done tonight

Welcome to Mandolin Co. 2008 年 3 月 1 日

2. 事



2.1 C#事件模型

C#使用一种委托模型来实现事件.事件处理方法不必在将生成事件的类中定义.设想应用 程序中有两个按钮,但这两个按钮的作用不同.如果事件处理程序被绑定到事件源,我们可 能必须写两个派生的按钮类,每个派生类有自己的事件处理程序.而在委托模型下,按钮类 仍是通用的,只是事件处理程序必须分别定义.事件处理程序可以(通常)放在不同的类中. 在委托模型下需要的是把事件源和事件处理程序连接起来的一种机制.这是委托发挥作 用的地方. 委托提供对指定了返回类型和参数列表的方法的一般引用. 方法做什么对委托并 不重要. 事件委托可以定义为生成事件的类的一个成员. 将用来处理事件的方法和事件处理 委托关联起来.当事件发生时,调用委托,然后调用事件处理方法. 事件处理委托是多播的. 事件的基本生命周期过程为:事件生成者把事件委托的一个实例定义为它的成员.事件 消费者是那些希望在事件发生时得到通知的对象. 它们定义将和事件委托关联的事件处理方 法.当生成事件时,事件生成者通过调用事件委托"触发"事件.然后委托调用和它关联的事 件处理方法. 事件委托把两个对象发送给每个事件处理方法.第一个是对生成事件的对象的引用,称 为事件源.第二个对象将是 System.EventArgs 类的一个实例,或者 EventArgs 的一个 派生类的实例.该对象包含了关于事件的额外信息.

2.2 事件委托

事件委托的一般形式:

<modifiers> delegate void <delegate_name>(object source, EventArgs e);

其参数列表中总是有两个参数. 第一个参数代表事件源. 第二个参数是 EventArgs 类的 一个实例, 或者它的派生类的一个实例, 它包含事件的另外的信息. 例如: MouseEventArgs 类定义下列属性: public MouseButtons Button { get; } public int Clicks { get; } public int Delta { get; } public int X { get;} public int Y { get;} 这些属性告诉事件处理程序哪个按钮引发了事件,事件中有几次单击,和事件发生的地 方.

.NET Framework 类库中的事件委托

这些内建的委托都派自 System.Delegate 类.下面是 System.Windows.Forms 命名空 间中包含的几个事件处理委托: public delegate void ColumnClickEventHandler(object source, ColumnClickEventArgs args) public delegate void DragEventHandler(object source, DragEventArgs args) public delegate void KeyEventHandler(object source, KeyEventArgs args) public delegate void MouseEventHandler(object source, MouseEventArgs args)

可知,所有这些内建的委托都遵循标准的事件委托格式:返回类型为 void,参数列表中 有两个参数.

用户定义的事件委托

您也可以按照事件委托的一般形式定义自己的事件委托.也可以定义自己的从 EventArgs 类派生的类, 包含和委托有关的信息. 例如: 如果想定义一个委托响应某个对象名称的变化, 可以这样定义: public delegate void NameEventHandler(object source, NameEventArgs args);

您还必须定义 NameEventArgs 类.

创建事件委托实例

事件委托实例的创建和标准委托的创建的不同之处是不使用 new 关键字, 而是使用 event 关键字.创建上面的委托的实例的语法是: public event NameEventHandler handler; event 关键字告诉编译器这个委托实例是一个事件.编译器将保证该委托拥有一个事件 委托的正确签名.编译器也将事件委托能进行的操作限制为+=和-=运算符.上面的语句创 建一个空引用,指向一个名为 handler 的事件委托.空状态表明还没有事件处理程序和这 个委托关联起来.

2.3 事件处理程序

事件处理程序是事件生成时事件委托调用的一个方法.事件处理方法可以在一个不同于 事件源的类中定义. 因为和事件委托关联在一起, 事件处理程序总是和事件委托有相同的参

数列表和返回类型. 要把事件处理程序和事件关联起来,事件必须在它维护的委托的列表中添加和方法相关 联的委托.例如:如果想把一个名为 NameChange()的事件处理程序和一个名为 handler 的 NameEventHandler 实例关联起来,使用的语法为: handler += new NameEventHandler(NameChange);

2.4 触发事件

要让类能够触发事件,应该把事件委托的一个实例定义为类的成员.生成事件的类应该定 义确定什么时候生成事件的代码,还应该定义生成提供事件的 EventArgs 对象的代码. 要触发事件,只需要调用事件委托实例.

3.用户定义的事件

这个例子中我们创建并应用一个用户定义的事件. NameList 类代表在列表中添加一个 字符串时生成事件的 ArrayList.ArrayList 被定义为一个字段.NameList 类还把一 个 NameListEventHandler 实例声明为字段. 除了构造函数, Add()方法是 NameList 类包含的唯一的一个函数成员. Add()方法把指定的字符串添加到 ArrayList.它接着查看是否有事件处理委托已经 添加到了 NameListEventHandler 实例.如果有的话,则调用该事件委托,委托又调 用这些方法,把对事件源的引用和 NameListEventArgs 类的一个实例传递给方法. NameListEventHandler 委托的第二个参数是一个 NameListEventArgs 对象.

NameListEventArgs 类封装了有关 NameList 类生成的事件的信息. 它定义了两个字 段,String 表示添加到列表中的名称,和一个整型值为列表中名称的当前数量. NameListEventArgs 类定义了一个公有的构造函数和两个属性返回字段的值.

using System; using System.Collections;

public delegate void NameListEventHandler(object source, NameListEve ntArgs args);

public class NameList { ArrayList list;

public event NameListEventHandler nameListEvent;

public NameList() { list = new ArrayList(); }

public void Add(string Name) { list.Add(Name); if (nameListEvent != null) { nameListEvent(this, new NameListEventArgs(Name, list.Count)); } } }

public class NameListEventArgs : EventArgs { string name; int count;

public NameListEventArgs(string str, int i) { name = str; count = i; }

public string Name { get { return name; } }

public int Count { get { return count; } } }

EventDemo 类创建一个 NameList 对象. NameList 对象的事件委托在它的列表中添 加两个事件处理委托.第一个引用 NewName()方法.第二个引用 CurrentCount()方法. 这两个方法都在 EventDemo 类中定义. 然后在 NameList 中添加两个名称.每添加一个名称,就触发一个事件,NewName() 和 CurrentCount()方法都被调用.这些方法输出被添加的名称和列表中名称的个数:

using System;

public class EventDemo { public static void Main() { NameList names = new NameList();

names.nameListEvent += new NameListEventHandler(NewName); names.nameListEvent += new NameListEventHandler(CurrentCount);

names.Add("Flowfield"); names.Add("Bosworth");

Console.ReadKey(); }

public static void NewName(object source, NameListEventArgs args) { Console.WriteLine(args.Name + " was added to the list"); }

public static void CurrentCount(object source, NameListEventArgs args)

{ Console.WriteLine("list currently has " + args.Count + " items"); } }

运行结果: Flowfield was added to the list list currently has 1 items Bosworth was added to the list list currently has 2 items

这个例子一个要注意的要点是,事件处理代码和事件源是完全分开的.您可以在完全不 改变 NameList 类本身的情况下,改变在 NameList 中添加一个元素时调用的方法.

摘自<<C# Programmer's Reference>>

posted on 2008-03-01 21:07 冷风 阅读(22) 评论(3) 编辑 收藏

发表评论

回复 引用 查看

2008-03-14 15:39 | leishiran [未注册用户 未注册用户] 未注册用户

毛病啊,computer 就 computer 嘛,干嘛非得搞点日语来个性化呢

回复 引用 查看

2008-03-15 00:15 | 冷风

老大,好歹你也是第一个给我博客留言,话点好听的嘛!

回复 引用 查看

2008-03-15 00:15 | starcloud [未注册用户 未注册用户] 未注册用户

顶一下再说

索引器(indexer) [C#]

1.索引器的作用是什么?

C#通过提供索引器,可以象处理数组一样处理对象.特别是属性,每一个元素 都以一个 get 或 set 方法暴露.最大的好处是使代码看上去更自然,更符合实 际的思考模式.索引器允许类或结构的实例按照与数组相同的方式进行索引. 索引器类似于属性,不同之处在于它们的访问器采用参数,如以下示例 2 所示.

2. 索引器的使用难吗?

索引指示器并不难使用.它们的用法跟数组相同.在一个类内部,你可以按照 你的意愿来管理一组数据的集合.这些对象可以是类成员的有限集合,也可以 是另外一个数组,或者是一些复杂的数据结构.不考虑类的内部实现,其数据 可以通过使用索引指示器来获得.

3.怎样定义一个索引器?

语法:类型 this[类型 参数] { get;set;} 举例:

public string this[int pos] { get { return strArray[pos]; } set

{ strArray[pos] = value; } }

4. 使用索引器

示例 1:

using System;

class Indexer { private string[] strArray;

public Indexer(int size) { strArray = new string[size]; for (int i = 0; i < size; i++) { strArray[i] = "empty"; } }

public string this[int pos] //声明一个索引器,索引器不支持 static,索引器只支持实 例 { get { return strArray[pos]; //返回一个值 } set { strArray[pos] = value; //value 是一个属性值 } }

static void Main(string[] args) { Indexer indexer = new Indexer(10); //创建对象 indexer indexer[9] = "Some Value"; //像对待数组一样对待对象 indexer //使用索引器 indexer[3] = "Another Value"; indexer[5] = "Any Value"; Console.WriteLine("Indexer Output:"); for (int i = 0; i < 10; i++) { Console.WriteLine("indexer[{0}]: {1}", i, indexer[i]); //使用索引器 } Console.ReadKey(); } }

运行结果: 运行结果: Indexer Output: indexer[0]: empty indexer[1]: empty indexer[2]: empty indexer[3]: Another Value indexer[4]: empty indexer[5]: Any Value indexer[6]: empty indexer[7]: empty indexer[8]: empty indexer[9]: Some Value

示例 2:

using System;

/// <summary> /// 员工类 /// </summary> public class Employee { private int age; private string name;

public Employee(int age, string name) { this.age = age; this.name = name;

}

public string Name { get { return name; } }

public int Age { get { return age; } }

public override string ToString() { return "Name:" + name + ", Age:" + age; } } /// <summary> /// 部门类 /// </summary> class Department { private int i = 0; private Employee[] employees;

public Department(params Employee[] emList) { employees = new Employee[emList.Length]; foreach (Employee e in emList) { employees[i++] = e; } } /// <summary> /// 创建索引器,通过 string 索引 /// </summary> /// <param name="name"></param> /// <returns></returns> public Employee this[string name] { get { foreach (Employee e in employees) { if (e.Name == name) return e; } return null; } } /// <summary> /// 创建索引器,通过 int 索引 /// </summary> /// <param name="age"></param>

/// <returns></returns> public Employee this[int index] { get { if ((index < 0) || (index > employees.Length - 1)) { return null; } return employees[index]; } } } /// <summary> /// 索引器测试类 /// </summary> class Application { static void Main(string[] args) { Employee[] empList = new Employee[3]; empList[0] = new Employee(20, "Lee yang"); empList[1] = new Employee(21, "Liu yang"); empList[2] = new Employee(22, "Lan yang");

Department dept = new Department(empList);

Console.WriteLine(dept["Jim"]); Console.WriteLine(dept["Liu yang"]); Console.WriteLine(dept[2]);

Console.ReadKey(); } }

运行结果: 运行结果

Name: Liu yang, Age: 21 Name: Lan yang, Age: 22

注意粗体 粗体部分的代码. 粗体 这样的话,我们就创建了索引器,我们就可以通过字符串或整型数索引来得到部门 (Department)里对应的员工(Employee)了.

摘自互联网

posted on 2008-02-29 08:45 冷风 阅读(6) 评论(1) 编辑 收藏

发表评论

回复 引用 查看

2008-02-29 17:21 | JefferyX

1. 覆盖与重载(override/overload) [C#] 2. 索引器(indexer) [C#] [引用提示]JefferyX 引用了该文章, 地址: http://www.cnblogs.com/JCSU/articles/1086412.html

反射(Reflection) [C#]

反射(Reflection)是.NET 中的重要机制,通过放射,可以在运行时获得.NET 中每一个 类型(包括类,结构,委托,接口和枚举等)的成员,包括方法,属性,事件,以及构造函 数等.还可以获得每个成员的名称,限定符和参数等.有了反射,即可对每一个类型了如指 掌.如果获得了构造函数的信息,即可直接创建对象,即使这个对象的类型在编译时还不知 道.

程序代码在编译后生成可执行的应用,我们首先要了解这种可执行应用程序的结构.

应用程序结构分为应用程序域—程序集—模块—类型—成员几个层次, 公共语言运行库加载 器管理应用程序域, 这种管理包括将每个程序集加载到相应的应用程序域以及控制每个程序 集中类型层次结构的内存布局.

程序集包含模块,而模块包含类型,类型又包含成员,反射则提供了封装程序集,模块和类 型的对象. 我们可以使用反射动态地创建类型的实例, 将类型绑定到现有对象或从现有对象 中获取类型,然后调用类型的方法或访问其字段和属性.反射通常具有以下用途.

(1)使用 Assembly 定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集 中查找类型并创建该类型的实例. (2)使用 Module 了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的 所有全局方法或其他特定的非全局方法. (3) 使用 ConstructorInfo 了解构造函数的名称, 参数, 访问修饰符 (如 pulic 或 private) 和实现详细信息(如 abstract 或 virtual)等.使用 Type 的 GetConstructors 或 GetConstructor 方法来调用特定的构造函数. (4) 使用 MethodInfo 了解方法的名称, 返回类型, 参数, 访问修饰符 (如 pulic 或 private) 和实现详细信息(如 abstract 或 virtual)等.使用 Type 的 GetMethods 或 GetMethod 方法来调用特定的方法. (5)使用 FiedInfo 了解字段的名称,访问修饰符(如 public 或 private)和实现详细信 息(如 static)等,并获取或设置字段值. (6)使用 EventInfo 了解事件的名称,事件处理程序数据类型,自定义属性,声明类型和 反射类型等,添加或移除事件处理程序. (7)使用 PropertyInfo 了解属性的名称,数据类型,声明类型,反射类型和只读或可写 状态等,获取或设置属性值. (8)使用 ParameterInfo 了解参数的名称,数据类型,是输入参数还是输出参数,以及 参数在方法签名中的位置等.

System.Reflection.Emit 命名空间的类提供了一种特殊形式的反射, 可以在运行时构造类 型. 反射也可用于创建称为类型浏览器的应用程序, 使用户能够选择类型, 然后查看有关选定类 型的信息. 此外,Jscript 等语言编译器使用反射来构造符号表.System.Runtime.Serialization 命 名 空 间 中 的 类 使 用 反 射 来 访 问 数 据 并 确 定 要 永 久 保 存 的 字 段 , System.Runtime.Remoting 命名空间中的类通过序列化来间接地使用反射.

反射提供了封装程序集,模块和类型的对象(Type 类型).可以使用反射动态创建类型的 实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性. 如果代码中使用了属性,可以利用反射对它们进行访问.有关更多信息,请参见属性.

下面是使用静态方法 GetType-- 从 Object 基类派生的所有类型都继承该方法 -- 获 取变量类型的简单反射示例:

C#

// Using GetType to obtain type information: int i = 42; System.Type type = i.GetType(); System.Console.WriteLine(type);

输出为:

System.Int32
此示例使用反射获取已加载的程序集的完整名称:

C#

// Using Reflection to get information from an Assembly: System.Reflection.Assembly o = System.Reflection.Assembly.Load("mscorlib.dll"); System.Console.WriteLine(o.GetName());
输出为:

mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 反射概述

反射在下列情况下很有用:

o o o o

需要访问程序元数据的属性.请参见主题使用反射访问属性. 检查和实例化程序集中的类型. 在运行时构建新类型.使用 System.Reflection.Emit 中的类. 执行后期绑定,访问在运行时创建的类型的方法.请参见主题动态加载和使用类型.

四:字符串 字符串

String(string)与 StringBuilder [C#] 与

1.string 与 String 的区别

C#提供了别名 string(s 小写)来代表 System.String 类. 如果在代码中使用 String, 必 须在代码一开始添加 using System; 这一行. 使用内建的别名 string 则不需要添加 using System;

2.

String 与 StringBuilder 的区别

string 对象是不可变的.包括长度或者其中的任何字符都是不可以改变的. 对象是不可变的.

string @string = "a"; @string = @string + "b";

以上代码让人误以为@string 是能够增加长度的.其实第二句代码是重新创建了一个新 的对象,而第一个对象被丢弃,它将成为垃圾收集器收集的对象.

C#中不能使用 new string()来构造一个 string 对象.假如可以,以上代码相当于:

string @string = new string("a"); @string = new string("a"+"b");

StringBuilder 是可变的.它是专门用于对字符串和字符执行动态操作的类.用 是可变的. StringBuilder 实现:

StringBuilder stringBuilder = new StringBuilder("a"); stringBuilder.Append("b");

StringBuilder 在内部有一个足够长的字符数组用于存放字符串对象,当字符串长度没 超过字符数组长度时,所有操作都是针对同一个字符数组.当长度超过时,StringBuilder 将自动创建一个更长的数组,把原来的数据复制到新数组中.

3.string 和 StringBuilder 性能比较

让 string 和 StringBuilder 执行相同的操作:循环追加一个字符串"a"

using System; using System.Collections.Generic; using System.Text;

namespace ConsoleApplication1 { class Program { private static void UseString() { string @string = "";

DateTime startTime = DateTime.Now;

for (int i = 0; i < 10000; i++) { @string += "a"; } DateTime endTime = DateTime.Now; TimeSpan timeSpan = endTime - startTime; Console.WriteLine(timeSpan.ToString()); } private static void UseStringBuilder() { StringBuilder stringBuilder = new StringBuilder();

DateTime startTime = DateTime.Now; for (int i = 0; i < 1000000; i++) { stringBuilder.Append("a"); } DateTime endTime = DateTime.Now; TimeSpan timeSpan = endTime - startTime; Console.WriteLine(timeSpan.ToString()); } static void Main(string[] args) { UseString(); UseStringBuilder(); Console.ReadKey(); } } }

运行得到以下比较结果:

运行次数 string StringBuilder 1 万次 一百万次

时间 0.078125 秒 0.046875 秒

结论:使用 StringBuilder 对字符串动态追加的速度是 string 的 200 倍左右
摘 http://blog.csdn.net/xiaomin98/archive/2008/01/15/2045002.aspx 自

五:类与接口

抽象类和接口的区别 [C#]

一,抽象类

(1) 抽象方法只作声明,而不包含实现,可以看成是没有实现体的虚方法 (2) 抽象类不能被实例化 (3) 抽象类可以但不是必须有抽象属性和抽象方法,但是一旦有了抽象方法,就一定要 把这个类声明为抽象类 (4) 具体派生类必须覆盖基类的抽象方法 (5) 抽象派生类可以覆盖基类的抽象方法,也可以不覆盖.如果不覆盖,则其具体派生 类必须覆盖它们.如:

using System;

public abstract class A //抽象类 A { private int num=0;

public int Num //抽象类包含属性 { get { return num; } set {

num = value; }

}

public virtual int getNum() //抽象类包含虚方法 { return num; }

public void setNum(int n) // //抽象类包含普通方法 { this.num = n; }

public abstract void E(); //类 A 中的抽象方法 E

}

public abstract class B : A //由于类 B 继承了类 A 中的抽象方法 E,所以类 B 也变成了抽象 类 {

}

public class C : B { public override void E() //重写从类 A 继承的抽象方法.如果类 B 自己还定义了抽象方 法,也必须重写 {

//throw new Exception("The method or operation is not implemented."); } }

public class Test { static void Main() { C c = new C(); c.E(); } }

二 ,接 口

(1) 接口不能被实例化 (2) 接口只能包含方法声明 (3) 接口的成员包括方法,属性,索引器,事件 (4) 接口中不能包含常量,字段(域),构造函数,析构函数,静态成员.如:

public delegate void EventHandler(object sender, Event e);

public interface ITest {

//int x = 0;

int A { get; set; }

void Test();

event EventHandler Event;

int this[int index] { get;

set; } }

(5) 接口中的所有成员默认为 public,因此接口中不能有 private 修饰符 (6) 派生类必须实现接口的所有成员 (7) 一个类可以直接实现多个接口,接口之间用逗号隔开 (8) 一个接口可以有多个父接口,实现该接口的类必须实现所有父接口中的所有成员

三,抽象类和接口

相同点: 相同点:

(1) 都可以被继承 (2) 都不能被实例化 (3) 都可以包含方法声明 (4) 派生类必须实现未实现的方法

区 别:

(1) 抽象基类可以定义字段,属性,方法实现.接口只能定义属性,索引器,事件,和 方法声明,不能包含字段. (2) 抽象类是一个不完整的类,需要进一步细化,而接口是一个行为规范.微软的自定 义接口总是后带 able 字段,证明其是表述一类"我能做..." (3) 接口可以被多重实现,抽象类只能被单一继承 (4) 抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现 某一功能的类中 (5) 抽象类是从一系列相关对象中抽象出来的概念, 因此反映的是事物的内部共性;接 口是为了满足外部调用而定义的一个功能约定, 因此反映的是事物的外部特性 (6) 接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法 (7) 接口可以用于支持回调,而继承并不具备这个特点 (8) 抽象类实现的具体方法默认为虚的, 但实现接口的类中的接口方法却默认为非虚的, 当然您也可以声明为虚的 (9) 如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实 现,而在抽象类的子类中实现接口中方法

使用规则: 使用规则:

1,抽象类主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能 2,如果要设计大的功能单元,则使用抽象类;如果要设计小而简练的功能块,则使用接 口. 3,如果预计要创建组件的多个版本,则创建抽象类.接口一旦创建就不能更改.如果 需要接口的新版本,必须创建一个全新的接口. 4,如果创建的功能将在大范围的全异对象间使用,则使用接口;如果要在组件的所有实 现间提供通用的已实现功能,则使用抽象类. 5,分析对象,提炼内部共性形成抽象类,用以表示对象本质,即"是什么".为外部提供 调用或功能需要扩充时优先使用接口 6,好的接口定义应该是具有专一功能性的,而不是多功能的,否则造成接口污染.如果 一个类只是实现了这个接口的中一个功能, 而不得不去实现接口中的其他方法, 就叫接口污 染 7,尽量避免使用继承来实现组建功能,而是使用黑箱复用,即对象组合.因为继承的层 次增多, 造成最直接的后果就是当你调用这个类群中某一类, 就必须把他们全部加载到栈中! 后果可想而知.(结合堆栈原理理解).同时,有心的朋友可以留意到微软在构建一个类时, 很多时候用到了对象组合的方法.比如 asp.net 中,Page 类,有 Server Request 等属 性, 但其实他们都是某个类的对象. 使用 Page 类的这个对象来调用另外的类的方法和属性, 这个是非常基本的一个设计原则 例 如: Window 窗体可以用抽象类来设计,可以把公有操作和属性放到一个抽象类里,让窗体 和对话框继承自这个抽象类,再根据自己的需求进行扩展和完善. 打印操作可以作为一个接口提供给每个需要此功能的窗体,因为窗体的内容不同,就要 根据他们自己的要求去实现自己的打印功能. 打印时只通过接口来调用, 而不用在乎是那个 窗体要打印.

四,其它文章

共性, 共性,个性与选择

有的书上写到 C#推荐使用接口(Interface)来替代抽象基类(Abstract Class),并 强调使用接口的诸多好处,这点我不敢苟同,从上面列表中看来,两者之间还是存在不少差 异的, 而这种差异的存在性必然决定了适用场景的不同, 例如在抽象基类中可以为部分方法 提供默认的实现,从而避免在子类中重复实现它们,提高代码的可重用性,这是抽象类的优 势所在; 而接口中只能包含抽象方法. 至于何时使用抽象基类何时使用接口关键还是取决于 用户是如何看待继承类之间的联系的, 用户更加关心的是它们之间的个性差异还是它们之间 的共性联系.举个生活中的例子加以说明.

如果给你三个对象分别是人,鱼,青蛙,让你为他们设计个基类来概括它们之间的联系, 那么首先给你的感觉肯定是它们个体间的差异性较大, 很难抽象出共性, 然而若让你概括他 们行为之间的共性,你可能想了想会意识到他们都会游泳,只不过是游泳方式迥异.那么这 时你就应当考虑使用接口而不是抽象基类,原因有三条:

1. 个性大于共性. 2. 差异较大的个性间具有某些相同的行为. 3. 相同行为的实现方式有较大区别. 这时再给你三个对象,分别是鲫鱼,鲤鱼,金鱼,仍然让你设计基类来概括它们之间的 联系, 那么你第一个意识到的肯定是它们都属于鱼类, 其次是他们游泳的方式可能稍有差异, 这时就应当使用抽象基类而不是接口,对比着上面的例子,原因也有三条:

interface ISwim { void Swim(); }

public class Person : ISwim { public void Swim() { //Swimming in person's style. } }

public class Frog : ISwim { public void Swim() { //Swimming in frog's style. } }

public class Fish : ISwim { public void Swim() { //Swimming in fish's style. } }

1. 共性大于个性 2. 共性相同的个体间必然具有相同的属性与行为 3. 相同行为的实现方式具有一定区别

abstract public class Fish { abstract public void Swim(); }

public class 鲫鱼 : Fish { public override void Swim() { //Swim like a 鲫鱼 } }

public class 鲤鱼 : Fish { public override void Swim() { //Swim like a 鲤鱼 } }

public class 金鱼 : Fish { public override void Swim() { //Swim like a 金鱼

} }

观察在使用接口或是使用抽象基类的几条理由中,第三条理由其实是一样的,它所描述 的是面向对象中多态的概念, 即通过覆盖父类的方法来实现, 在运行时根据传递的对象引用, 来调用相应的方法. 第二条理由开始产生分歧, 接口更加强调了继承对象间具有相同的行为, 而抽象类同时还强调了继承对象间具有相同的属性. 而真正将接口与抽象基类区分开的则是 理由一,归纳如下:

1. 当在差异较大的对象间寻求功能上的共性时,使用接口. 2. 当在共性较多的对象间寻求功能上的差异时,使用抽象基类.

C#2.0 中的静态类 static class [C#]

C# 2.0 提供了静态类,在 1.x 中我们要实现静态类需要使用下面的代码.

1.0

public sealed class Class1 { private Class1(){} }

在 C# 2.0 中我们可以使用 static class 来更加优雅地解决这个问题.

public static class Class1 { }

我们反编译一下,会了解其实现机制.

.class public abstract auto ansi sealed beforefieldinit Program extends object { }

原来编译器将该类声明为 abstract sealed,自然不能被继承被实例化了. 但是 C#编译器并不允许我们在代码中直接声明一个 abstract sealed 类型, 下面的代码无 法通过编译.

public abstract sealed class Class1 { }

静态类的限制

1. 静态类不能有实例构造器. 2. 静态类不能有任何实例成员. 3. 静态类上不能使用 abstract 和 sealed 修饰符. 4. 静态类默认继承自 System.Object,不能显式指定任何其他基类. 5. 静态类不能指定任何接口实现. 6. 静态类的成员不能有 protected 或者 protected internal 访问保护修饰符.

posted on 2008-03-09 23:25 冷风 阅读(22) 评论(0) 编辑 收藏

六:工具集使用

MSIL 反汇编程序 (Ildasm.exe) [C#]

MSIL

MSIL 是.NET Framework 中最重要的概念之一. 通过 MSIL 可以预知不同.NET 语言的执 行效率应该是接近的. 通过 MSIL 汇编程序(ilasm.exe)和 MSIL 反汇编程序(dilasm.exe) 可以观察.NET 应用的内部构造,因此也可以用于.NET 应用,组件的破解.

ILDASM 的使用方法

步骤一:开始 → 所有程序 → Microsoft Visual Studio 2005 → Visual Studio Tools → Visual Studio 2005 命令提示

步骤二: 假设我们有一个控制台应用程序 Test.exe. 然后把 Test.exe 拷贝到 D:\Program Files\Microsoft Visual Studio 8\VC 目录下

步骤三:在命令提示窗口进行下面例子中的操作.例如: ildasm Test.exe 将反汇编代码显示在 Ildasm.exe 的默认 GUI 中 ildasm Test.exe /output:Test.il 将结果 MSIL 汇编程序文本存储在 Test.il 文件 中 ildasm Test.exe /text 将反汇编结果显示到控制台窗口 ildasm /item:Test::Print Test.exe /text 将 Test.exe 中类 Test 中的方法 Print 进 行反汇编

ILDASM 的使用示例

下面的命令使 PE 文件 MyHello.exe 的元数据和反汇编代码显示在 Ildasm.exe 的默 认 GUI 中.

ildasm myHello.exe 默认的 GUI 为下图所示:

GUI 中显示的 IL 代码

下面的命令对 MyFile.exe 文件进行反汇编,并将结果 MSIL 汇编程序文本存储在

MyFile.il 文件中. ildasm MyFile.exe /output:MyFile.il
下面的命令对 MyFile.exe 文件进行反汇编,并将结果 MSIL 汇编程序文本显示到控制 台窗口中.

ildasm MyFile.exe /text 如果文件 MyApp.exe 包含嵌入的托管和非托管资源,则下面的命令将产生以下 4 个文 件:MyApp.il,MyApp.res,Icons.resources, 和 Message.resources:

ildasm MyApp.exe /output:MyApp.il 下面的命令对 MyFile.exe 的 MyClass 类中的 MyMethod 方法进行反汇编,并将输出 显示到控制台窗口中.

ildasm /item:MyClass::MyMethod MyFile.exe /text 在上面的示例中,可能有几个具有不同签名的 MyMethod 方法.下面的命令对返回类型为 void 且带有参数 int32 和 System.String 的 MyMethod 方法进行反汇编.

ildasm /item:"MyClass::MyMethod(void(int32,class System.String))" MyFile.exe /text

posted on 2008-03-11 22:25 冷风 阅读(23) 评论(0) 编辑 收藏

相关文章:
第一章 .NET基础及C#基本语法
第一章 .NET 基础C#基本语法 #一、.NET 概述 1.Microsoft .NET 框架 (...(控制台应用程序适于初 学者学习面向对象的概念方面的基础知识) (2).编写第一...
C#基础知识大全
C#基础知识大全_调查/报告_表格/模板_实用文档。C#基础知识大全,入门者的提升基石 C#基础知识基础知识之.NetFramework 简介 基础知识之 .netframework 运行于...
C#(.NET)知识知识总结
C#(.NET)知识知识总结_IT/计算机_专业资料。第一章:C#的介绍以及异常处理第二章:C#的类与对象的关系,以及属性与方法(静态方法,构造方法,重载) 第三章:C#基本...
c#基础知识教材-asp_net经典教材
c#基础知识教材-asp_net经典教材_计算机软件及应用_IT/计算机_专业资料。C#语言基础第一章 Windows编程的基础知识第二章 常用控件和类的使用第三章 文本编辑器的...
DotNet题库(c#基础)
DotNet测试题 6页 2财富值 C#试题库最全版 111页 免费如要投诉违规内容,请到...2.根据线程安全的相关知识,分析以下代码,当调用 test 方法时 i>10 时是否会...
《C#.NET程序设计》教案
计算机 称 C#.NET 程序设计 师 张赞波 务 副教授 材 C#程序设计教程 教 研...6. 初步掌握前面所学知识的应用。 教学内容(包括基本内容、重点、难点) : 1....
C#完全零基础入门精讲完整版 【优品课堂】免费学习_.NE...
让熟悉基本电脑操作,但没有任何编程基础的同学,在短时间内掌握C#编程语言,为从事各方向的.NET开发打下基础。视频教程,优特教育全套教学,在线学习.NET课程,C#完全...
DotNet试题(c#基础参考答案)
25.net 的错误处理机制是什么? Try {} Catch {} Finally {} 26.描述一下 C#中索引器的实现过程,是否只能根据数字进行索引? Public class aa { private ...
【1】零基础学好C#核心语法【.NET极速开发实战系列】
学完后能够快速掌握C#项目开发中所需要的各种语法知识。视频教程,喜科堂互联教育全套教学,在线学习C#课程,【1】零基础学好C#核心语法【.NET极速开发实战系列】视频下载
《Visual C#.NET开发技术》复习提纲和模拟练习
《Visual C#.NET开发技术》复习提纲和模拟练习_电脑基础知识_IT/计算机_专业资料。《Visual C#.NET 开发技术》复习提纲和模拟练习考试题型:一、单项选择题(每小题...
更多相关标签: