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

最近在看垃圾回收的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


如果本文对您有帮助,可以扫描下方二维码打赏!您的支持是我的动力!
微信打赏 支付宝打赏

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注