最近在看垃圾回收的IDispose的接口实现,平时经常听到某个类型要执行什么操作需要实现什么接口,某个类型实现了什么接口。但我们用的系统提供的类型都已经实现了这些接口,但到底怎么实现的我们不清楚。所以我们就把这些常用的接口实现在自己的类型中。让他们具有一定功能,也让我认识下这些接口。
下面的例子中一共实现了IComparable,IComparer,IEnumerable,IEnumerator和IDisposable,实现这些接口,我们的类型就可以在List或Array中进行排序,使用foreach进行遍历,手动释放资源或使用using块。在看代码前先说明下,2.0中增加了泛型的支持,所以也有了泛型的接口。我下面说的主要是针对接口,而不管是不是泛型(区别不大),但代码中都是实现的泛型接口。所以看到介绍是非泛型,代码是泛型不要误会,我主要是介绍接口以及实现。
一 实现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);
}
}
}
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("");
}
类型实现了可被排序的功能,其中不传递排序字段时是按默认比较器排序的,而如果指定了,则是按自己的实现来排序。而指定时是通过生成嵌套的迭代类的实例来指示要排序的字段.
====实现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
二 实现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()
{
}
}
}
三 实现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