自定义类型实现系统接口(一)

 最近在看垃圾回收的IDispose的接口实现,平时经常听到某个类型要执行什么操作需要实现什么接口,某个类型实现了什么接口。但我们用的系统提供的类型都已经实现了这些接口,但到底怎么实现的我们不清楚。所以我们就把这些常用的接口实现在自己的类型中。让他们具有一定功能,也让我认识下这些接口。

下面的例子中一共实现了IComparable,IComparer,IEnumerable,IEnumerator和IDisposable,实现这些接口,我们的类型就可以在List或Array中进行排序,使用foreach进行遍历,手动释放资源或使用using块。在看代码前先说明下,2.0中增加了泛型的支持,所以也有了泛型的接口。我下面说的主要是针对接口,而不管是不是泛型(区别不大),但代码中都是实现的泛型接口。所以看到介绍是非泛型,代码是泛型不要误会,我主要是介绍接口以及实现。

 

1:实现IComparable,IComparer接口

IComparable:定义由值类型或类实现的通用的比较方法,以为排序实例创建类型特定的比较方法。

IComparer:定义类型为比较两个对象而实现的方法。

通常我们要在数组或链表中对一个对象进行排序,这就需要对对象进行比较。但因为数组和链表可以存放各种数据,值类型,引用类型,或是我们自建类型,所以它不可能去实现各个类型的比较方法,而需要类型自己实现。所以,.NET中如果一个类型实现了IComparable接口,那么他就可以被排序,因为排序算法知道去调用这个类型的CompareTo方法。

但是有时候我们类型有多个字段,需要能选择的进行排序,这个时候就需要在此类型中实现IComparer接口,以便按自己的方式进行排序。如果不实现此接口,系统会自己产生一个默认的实现。但是要注意的是2个接口的实现不是直接在一个类上,而是通过嵌套类。

/// <summary> /// 实现IComparable和IComparer接口,使得改类型对象可以List,Array等类型的sort排序方法使用 /// 代码中有2个CompareTo方法,其中一个是使用默认比较器不需要自己实现IComparer;另一个需要设置一个比较字段,要自己实现IComparer接口的Compare方法。 /// </summary> #region class test : IComparable<test> { string name; int age; int height; public test(string name, int age, int height) { this.name = name; this.age = age; this.height = height; } /// <summary> /// 使用默认比较器的实现 /// </summary> /// <param name="other">要比较的字段</param> /// <returns></returns> public int CompareTo(test other) { return this.age.CompareTo(other.age); } /// <summary> /// 使用指定比较器testComparer的实现 /// </summary> /// <param name="other">要比较的对象</param> /// <param name="which">要比较的字段</param> /// <returns></returns> public int CompareTo(test other, testComparer.CompareType which) { switch (which) { case testComparer.CompareType.Age: return this.age.CompareTo(other.age); case testComparer.CompareType.Height: return this.height.CompareTo(other.height); } return 0; } /// <summary> /// 获取比较器 /// </summary> /// <returns></returns> public static testComparer GetComparer() { return new test.testComparer(); } public void print() { Console.WriteLine("name:{0},age:{1},height:{2}", this.name, this.age, this.height); } /// <summary> /// 实现比较类型的选择 /// 实现Compare是通过一个内嵌类来实现IComparer接口的。类中还定义了要比较的字段的枚举。而Compare则调用CompareTo方法 /// </summary> public class testComparer : IComparer<test> { //存放要比较的字段 private CompareType _whichComparer; //要比较的字段枚举 public enum CompareType { Age, Height } //设置要比较的字段 public CompareType whichComparer { get { return _whichComparer; } set { _whichComparer = value; } } /// <summary> /// 比较器,调用需要比较字段的实现方法 /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <returns></returns> public int Compare(test x, test y) { return x.CompareTo(y, whichComparer); } } }

 

 

2:实现IEnumerable,IEnumerator接口

IEnumerable:公开枚举数,该枚举数支持在指定类型的集合上进行简单迭代。

IEnumerator:支持非泛型集合上进行简单迭代。

这2个接口是为我们的类型提供可枚举迭代的功能,也就是我们可以用foreach来访问我们的类型中的数据。其中IEnumerable只是返回一个枚举集合,表示这个类型是一个可枚举的,而要要实现迭代功能的是要通过IEnumerator实现的。下面是1.1和2.0中不同的实现。

在2.0中增加yield关键字(代码中yield竟然不是蓝色,看来CSDN还没到2.0啊,哈哈), 所以我们自己只需要实现IEnumerable接口,yield关键字为我们自动实现了IEnumerator接口。大家可以编译1.1和2.0的方法,然后使用ILDASM查看。

/// <summary> /// .NET2.0实现IEnumerable和IEnumerator接口,使得类型可以被枚举。 /// 只用实现IEnumerable接口中的GetEnumerator方法,此方法中使用yield关键字,系统会自动生成内嵌的迭代器类。 /// </summary> #region class testList2 : IEnumerable<test> { test[] tlist; //testList用来存放test对象的列表 //初始化testList public testList2(params test[] tl) { tlist = new test[tl.Length]; for (int i = 0; i < tl.Length; i++) { tlist[i] = tl[i]; } } /// <summary> /// /// 隐示的实现泛型方法 /// </summary> /// <returns></returns> public IEnumerator<test> GetEnumerator() { for (int i = 0; i < tlist.Length; i++) yield return tlist[i]; } /// <summary> /// 显示的实现非泛型方法 /// </summary> /// <returns></returns> IEnumerator IEnumerable.GetEnumerator() { for (int i = 0; i < tlist.Length; i++) yield return tlist[i]; } }

 

这里是.NET1.1中的实现,我们需要自己是实现迭代的过程,方法是在克枚举类型中定义一个内嵌类,来实现IEnumerator接口,实现具体的迭代关系。

 /// <summary> /// .NET1.0实现IEnumerable和IEnumerator接口,使得类型可以被枚举。 /// 本身实现IEnumerable接口中的GetEnumerator方法,还需要利用嵌套手动实现迭代器,实现 IEnumerator接口 /// </summary> #region class testList: IEnumerable<test> { test[] tlist; //testList用来存放test对象的列表 //初始化testList public testList(params test[] tl) { tlist = new test[tl.Length]; for (int i = 0; i < tl.Length; i++) { tlist[i] = tl[i]; } } /// <summary> /// 实现IEnumerable接口的GetEnumerator方法,此方法返回一个迭代器的具体实现对象,而2.0中直接使用yeild /// </summary> /// <returns>获得一个迭代器</returns> public IEnumerator<test> GetEnumerator() { return new MyEnumerator(this); //这里this是testList类对象,传递给迭代器实现迭代 } IEnumerator IEnumerable.GetEnumerator() { return new MyEnumerator(this); //这里this是testList类对象,传递给迭代器实现迭代 } /// <summary> /// .NET1.1中迭代器的实现是利用一个嵌套类,实现IEnumerator接口的3个方法,对象返回给IEnumerable接口的GetEnumerator方法 /// 但实际1.1中没有泛型,这里是按1.1的方式实现了2.0中的泛型接口。 /// </summary> #region class MyEnumerator : IEnumerator<test> { testList pl; //具有迭代的类型对象 int p_Current; //迭代的位置 /// <summary> /// 构造器,获得一个具有迭代的类型对象 /// </summary> /// <param name="pl"></param> public MyEnumerator(testList pl) { this.pl = pl; this.p_Current = -1; } /// <summary> /// 显示实现泛型Current方法 /// </summary> public test Current { get { if (p_Current == -1) throw new InvalidOperationException(); return pl.tlist[p_Current]; } } /// <summary> /// 隐示实现object型Current方法,实现2个方法未来兼容 /// </summary> object IEnumerator.Current { get { if (p_Current == -1) throw new InvalidOperationException(); return pl.tlist[p_Current]; } } /// <summary> /// 是枚举器向后移动 /// </summary> /// <returns></returns> public bool MoveNext() { p_Current++; if (p_Current < pl.tlist.Length) return true; else return false; } /// <summary> /// Reset 方法是为 COM 交互操作而提供的。没有必要将其实现.调用Reset方法会引发 InvalidOperationException /// </summary> public void Reset() { p_Current = -1; } public void Dispose() { } } }

 

3:实现IDisposable接口

IDisposable:定义一种释放分配的非托管资源的方法

法。看到下面的类并没有指定 :IDisposable但可以正常运行,而上面的例子如果去掉就会提示没有实现XX接口....。这个主要和调用有关的,上面类型,比如排序的时候调用比较方法是通过接口类型来调用的,而非类型本身的 类型。而在释放资源时是通过本类型来调用的。但还是建议指定继承与接口,这样就可以强制你必须实现这个功能。否则你无法使用useing块。

Dispose,close,Finalize是我们常见的3中资源回收的方法,其中FinaliFinalize是CLR的回收机制,他主要是用来回收本地非托管资源(文件句柄,图像资源等),无法控制执行,我们在类型中使用~类名()这样的形式来定义Finalize,这个和C++的析构函数很象,但原理完全不一样,他是系统在垃圾回收或CLR关闭等情况下被调用的。但有些时候我们知道资源已经用完,比如文件已经写入,这个时候我们就可以手动回收资源,而不要垃圾回收期回收,以免垃圾回收器的代龄被提高。Dispose是我们提供的一个显式的释放资源的方法。而Close方法和Dispose实现是一样的。一般实现了Finalize的都需要实现Dispose释放模式,但实现了释放模式的不一定需要实现Finalize。最后要注意的是Dispose和Finilaze方法中不要引用其他类型的的释放或终结方法,因为总结方法调用是系统决定的,先后顺序是不定的,所以容易出现错误。

/// <summary> /// 实现IDisposable接口(修改MSDN的例子) /// 完成Dispose,close,Finalize。 /// </summary> #region class testDispose { //类型中可以不实现Finalize而实现Dispose。Finalize是CLR的机制,是系统在垃圾回收或CLR关闭等情况调用,无法控制。而Dispose是我们提供的一个释放方法。 //用来显示的回收本地资源。 // 类型中的非托管资源(本地资源) private IntPtr handle; // 类型中用到的托管资源 private Component component = new Component(); // 是否执行释放 private bool hasdisposed = false; public testDispose(IntPtr handle) { this.handle = handle; } /// <summary> /// 实现IDisposable接口,不要设置为虚方法,防止派生类重写此方法。 /// 定义两个Dispose利用了设计模式的重载。 /// </summary> public void Dispose() { //调用带参数的Dispose方法回托管和非托管收所有资源 Dispose(true); //这是是自己使用Dispose释放资源,所以使用此方法阻止垃圾回收器进行终结操作 GC.SuppressFinalize(this); } /// <summary> /// 带参数的Dispose方法,具体释放资源代码卸载这里 /// </summary> /// <param name="disposing">如果为true则标识自己调用释放方法回收资源,如果为false标识垃圾回收器执行终结操作</param> private void Dispose(bool disposing) { // 判断是否执行过释放操作,如果没有则执行,否则直接返回 if (!hasdisposed) { Console.WriteLine("开始进行资源回收....."); if (disposing) { Console.WriteLine("开始放托管资源..."); //是释放操作则可以释放托管资源 component.Dispose(); Thread.Sleep(1000); Console.WriteLine("完成释放托管资源"); } //否则只释非托管放本地资源,而不释放托管资源,因为不是自己释放,无法确定托管资源是否被回收 Console.WriteLine("开始放本地资源..."); CloseHandle(handle); handle = IntPtr.Zero; Thread.Sleep(1000); Console.WriteLine("完成释放本地资源"); //设置已释放过 hasdisposed = true; } else { Console.WriteLine("已经进行过资源释放....."); } } /// <summary> /// 提供Close方法实现,实现同Dispose相同。 /// </summary> public void Close() { Dispose(); } //调用API完成关闭本地资源句柄操作 [System.Runtime.InteropServices.DllImport("Kernel32")] private extern static Boolean CloseHandle(IntPtr handle); /// <summary> /// C#中的析构方法,被编译为Finalize(),不能显示调用,只在垃圾回收时被系统调用 /// 这里仅仅调用Dispose方法 /// </summary> ~testDispose() { //这里参数为false因为析构方法是系统调用,所以不确定调用时间,无法确定托管资源是否回收,所以不释放托管资源。 Dispose(false); } }

 

以上是使用我们自己的类型实现了这些接口,下面就可以看下这些接口给类型带来的实际效果。

static void Main(string[] args) { /// ///实现IComparable和IComparer接口 /// test t1 = new test("cc", 24, 168); test t2 = new test("yy", 15, 187); test t3 = new test("dd", 34, 167); Console.WriteLine("====实现IComparable和IComparer接口====/n"); List<test> ln = new List<test>(); ln.Add(t1); ln.Add(t2); ln.Add(t3); foreach (test t in ln) t.print(); Console.WriteLine("================default sort==================="); ln.Sort(); foreach (test t in ln) t.print(); Console.WriteLine("================height sort==================="); test.testComparer tc = test.GetComparer(); tc.whichComparer = test.testComparer.CompareType.Height; ln.Sort(tc); foreach (test t in ln) t.print(); Console.WriteLine(""); }

 

 

=====.NET2.0实现IEnumerable和IEnumerator接口==== name:cc,age:24,height:168 name:yy,age:15,height:187 name:dd,age:34,height:167 =====.NET1.1实现IEnumerable和IEnumerator接口==== name:cc,age:24,height:168 name:yy,age:15,height:187 name:dd,age:34,height:167

 

我们看到了2个版本的实现效果完全一样,而且都可以使用foeach或枚举对象的方法来访问。当使用枚举对象时我们可以用泛型或非泛型来操作。不同大家应该可以看的到,非泛型是Object,不能直接调用test类型的print方法,需要强制转化。所以在性能上泛型接口还是要好一些。但是我们程序中实现的是泛型接口而不是非泛型的啊。观察类型实现代码可以发现,我们实现了泛型和非泛型2种方法,这个是系统要求的,是未来确保兼容性。因为泛型接口实际也是继承与非泛型接口的。

 

 

public interface IEnumerator<T> : IDisposable, IEnumerator { T Current { get; } }

 

最后一个就是垃圾回收的情况了,分别演示了手动回收,对已回收的对象进行回收,使用垃圾回收器自动回收,和使用using块。这里注意的时使用using块必须实现IDisposable,否则系统提示 using 语句中使用的类型必须可隐式转换为"System.IDisposable”。代码中GC.collect()是为了演示,实际上实现了Dispose我们是不需要手动进行垃圾回收的。完全由垃圾回收器自己进行。

 

 

static void Main(string[] args) { /// ///实现 IDisposable接口 /// Console.WriteLine("==========实现IDisposable接口==========/n"); IntPtr ip = new IntPtr(); testDispose td = new testDispose(ip); IntPtr ip2 = new IntPtr(); testDispose td2 = new testDispose(ip2); IntPtr ip3 = new IntPtr(); Console.WriteLine("====手动释放资源===="); td.Dispose(); Console.WriteLine("====手动再次释放资源===="); td.Close(); Console.WriteLine("====使用using释放资源===="); using (testDispose td3 = new testDispose(ip3)) { Console.WriteLine("using块执行完成"); } Console.WriteLine("====系统释放资源===="); GC.Collect(); Console.ReadKey(); }

 

==========实现IDisposable接口========== ====手动释放资源==== 开始进行资源回收..... 开始放托管资源... 完成释放托管资源 开始放本地资源... 完成释放本地资源 ====手动再次释放资源==== 已经进行过资源释放..... ====使用using释放资源==== using块执行完成 开始进行资源回收..... 开始放托管资源... 完成释放托管资源 开始放本地资源... 完成释放本地资源 ====系统释放资源==== 开始放本地资源... 完成释放本地资源

 

 

我们可以看前面类型中有本地非托管资源和托管资源。使用Dispose,Close和using块效果是一样的,对托管和非托管的资源进行回收。而对一个已回收的对象资源在进行回收是没有效果的。这里也可以看到,对资源进行释放并没有把对象从GC堆上删除。要明白的是所有的Dispose,Finilaze操作都只是回收资源。而不是释放内存空间。任何对象的内存空间,都是CLR进行垃圾回收的。我们显示释放资源,只是为了对象能尽早的被垃圾回收,以免提高代龄(因为垃圾回收时,如果对象引用了其他的资源,需要首先回收资源,下一次垃圾回收才进行内存回收。而资源释放是系统控制的,如果不手动释放,可能会增长垃圾对象在GC堆中的时间,甚至发生复苏或提高代龄)。

源代码下载地址:http://download.csdn.net/source/797668

 

 

 

====实现IComparable和IComparer接口==== name:cc,age:24,height:168 name:yy,age:15,height:187 name:dd,age:34,height:167 ================default sort=================== name:yy,age:15,height:187 name:cc,age:24,height:168 name:dd,age:34,height:167 ================height sort=================== name:dd,age:34,height:167 name:cc,age:24,height:168 name:yy,age:15,height:187

类型实现了可被排序的功能,其中不传递排序字段时是按默认比较器排序的,而如果指定了,则是按自己的实现来排序。而指定时是通过生成嵌套的迭代类的实例来指示要排序的字段.

static void Main(string[] args) { /// ///实现IEnumerable和IEnumerator接口 /// test t1 = new test("cc", 24, 168); test t2 = new test("yy", 15, 187); test t3 = new test("dd", 34, 167); Console.WriteLine("=====.NET2.0实现IEnumerable和IEnumerator接口====/n"); testList2 tls = new testList2(t1, t2, t3); foreach (test t in tls) t.print(); //另外的调用方法 Console.WriteLine("=====.NET1.1实现IEnumerable和IEnumerator接口====/n"); testList ts = new testList(t1, t2, t3); IEnumerator<test> itl = ts.GetEnumerator(); //支持泛型 //IEnumerator itl2 = ts.GetEnumerator(); //也支持非泛型 while (itl.MoveNext()) { //泛型直接调用 itl.Current.print(); //非泛型需要转化 //test tt = (test)itl2.Current; //tt.print(); } Console.WriteLine(""); }

 

 

 

你可能感兴趣的:(设计模式,.net,String,object,Class,interface)