代码改变世界

C#泛型

2010-05-16 18:08  杨平  阅读(4855)  评论(2编辑  收藏  举报

目录

  • 创建泛型类
  • 泛型类的特性
  • 泛型接口
  • 泛型方法
  • 泛型委托

泛型并不是一个全新的结构,其他语言中也有类似的概念,比如C++中的模板,但C++中的模板和.NET中的泛型还是有很的的区别的,下面就对泛型做些研究。

  • 创建泛型类

学习数据结构时,最为常见的就是链表,我们就以链表为例来描述泛型类创建的细节。首先,我们知道,链表是由不定数目的结点连接而成的,结点中有数据字段和指向下一个或上一个结点的对象应用,下面是双向链表结点类型代码:

    public class LinkedListNode
    {        
        public LinkedListNode(Object value)
        {
            this.value = value;
        }

        //结点数据值
        private object value;
        public object Value 
        {
            get 
            {
                return this.value;
            }
        }

        //双向结点指向引用
        private LinkedListNode next;
        private LinkedListNode pre;
        public LinkedListNode Next 
        {
            get { return this.next; }
            internal set { this.next = value;  }
        }
        public LinkedListNode Pre
        {
            get { return this.pre; }
            internal set { this.pre = value; }
        }
    }

其中,结点的数据存储在Object类型的Value字段中。下面我们来创建链表类,该链表类有一个头结点、尾结点和在链表的末尾添加/删除结点的方法:

    //双向链表类
    public class LinkedList 
    {
        //头结点
        private LinkedListNode head;
        public LinkedListNode Head 
        {
            get { return this.head; }
        }
        //尾结点
        private LinkedListNode last;
        public LinkedListNode Last
        {
            get { return this.last; }
        }

        //在链表尾插入结点
        public LinkedListNode AddNode(Object value) 
        {
            LinkedListNode node = new LinkedListNode(value);

            if (head == null)
            {
                head = node;
                last = head;
            }
            else
            {
                last.Next = node;
                node.Pre = last;
                last = node;
            }

            return last;
        }

        //在链表尾删除结点
        public LinkedListNode SubNode()
        {
            if (last == null)
            {
                return null;
            }
            else
            {
                LinkedListNode node = last.Pre;
                node.Next = null;
                last = node;
            }

            return last;
        }
    }

因为是链表,所以我们希望能使用foreach遍历链表,那么,将链表类LinkedList继承IEnumerable接口并实现GetEnumerator()方法:

    public class LinkedList : IEnumerable
    {
        ...

        public IEnumerator GetEnumerator()
        {
            LinkedListNode curr = head;
            while (curr != null)
            {
                yield return curr.Value;
                curr = curr.Next;
            }
        }
    }

全部代码:



这样我们就可以使用链表了,但该链表中的存储数据只能使Object类型的,这就并不能保证链表中的数据是类型统一的,因此在使用foreach遍历链表时可能出现致命的错误。比如该链表中有整形数据和字符串数据。而且,该链表并不能够为我们提供一个标准的模板。

下面,我们改进LinkedListNode结点类,使其使用泛型——存储数据的类型使我们任意指定的类型:

    public class LinkedListNode<T>
    {
        public LinkedListNode(T value)
        {
            this.value = value;
        }

        //结点数据值
        private T value;
        public T Value 
        {
            get 
            {
                return this.value;
            }
        }

        //双向结点指向引用
        private LinkedListNode<T> next;
        private LinkedListNode<T> pre;
        public LinkedListNode<T> Next 
        {
            get { return this.next; }
            internal set { this.next = value;  }
        }
        public LinkedListNode<T> Pre
        {
            get { return this.pre; }
            internal set { this.pre = value; }
        }
    }

同样,也需要将链表类改为泛型类:

    public class LinkedList<T> : IEnumerable<T>
    {
        //头结点
        private LinkedListNode<T> head;
        public LinkedListNode<T> Head 
        {
            get { return this.head; }
        }
        //尾结点
        private LinkedListNode<T> last;
        public LinkedListNode<T> Last
        {
            get { return this.last; }
        }

        //在链表尾插入结点
        public LinkedListNode<T> AddNode(T value) 
        {
            LinkedListNode<T> node = new LinkedListNode<T>(value);

            if (head == null)
            {
                head = node;
                last = head;
            }
            else
            {
                last.Next = node;
                node.Pre = last;
                last = node;
            }

            return last;
        }

        //在链表尾删除结点
        public LinkedListNode<T> SubNode()
        {
            if (last == null)
            {
                return null;
            }
            else
            {
                LinkedListNode<T> node = last.Pre;
                node.Next = null;
                last = node;
            }

            return last;
        }

        #region IEnumerable 成员

        public IEnumerator<T> GetEnumerator()
        {
            LinkedListNode<T> curr = head;
            while (curr != null)
            {
                yield return curr.Value;
                curr = curr.Next;
            }
        }

        IEnumerator IEnumerable.GetEnumerator() 
        {
            return GetEnumerator();
        }
        #endregion
    }

这样我们就保证了链表中数据的统一性,因此可以安全的使用foreach遍历链表了。

其实,我个人认为,泛型也就是我们在编写代码时并不知道要使用的数据类型是什么,而是在使用代码时规定数据类型的,这样,我们就很容易的创建复用的模板代码,即减少了代码编写的工作,也节省了空间资源。

  • 泛型类特性

到此为止,相信大家已经对泛型有了较为具体的概念了,接下来,继续我们的研究。

(1)默认值

我们知道,泛型类中,可以使用任意数据类型,比如引用类型和值类型。那么泛型有没有默认只呢?很显然,我们不能单纯的只将null赋值给泛型——非可空值类型的默认值并不是null。

这里我们可以用——default——关键字来获取泛型的默认值(null或0)来初始化泛型,格式如下:

    public class LinkedListNode<T>
    {
        public LinkedListNode(T value)
        {
            this.value = default(T);
            ...
        }

        //结点数据值
        private T value;
        ...
    }

(2)约束

我们的链表可以使用foreach进行遍历了,但作为集合类,可能还需要排序等功能,那么就需要结点与结点之间也能进行比较,这样我们就需要将节点类实现IComparable<T>泛型接口了,具体代码如下:

    public class LinkedListNode<T> : 
        IComparable<LinkedListNode<T>>
    {
        ...
        #region IComparable<LinkedListNode<T>> 成员
        public int CompareTo(LinkedListNode<T> other)
        {
            return other.value.CompareTo(other.value);
        }
        #endregion
    }

这样我们就可以在值类型结点与结点之间直接比较大小了。但是,我们又会发现另一个问题——如果泛型T数据类型不是值类型的或是较为复杂的引用类型时(换句话说就是该数据类型对象之间不能直接比较),那么结点之间的直接比较就会出错了,怎么办?当然,我们只需要给T类型的数据添加一个约束就行了:

    public class LinkedListNode<T> : 
        IComparable<LinkedListNode<T>> where T:IComparable<T>
    {
        ...
    }

同样,泛型链表类也需要添加约束:

    public class LinkedList<T> : IEnumerable<T> where T:IComparable<T>
    {
        ...
    }

 

添加约束后,这样结点与结点之间直接比较就有了安全保障了。上述代码中,我们给泛型数据类型T添加约束后,就规定在使用链表时必须传入已经实现了IComparable<T>泛型接口的数据类型。

(3)继承

泛型类型可以实现泛型接口,也可以派生于一个类。

泛型类型可以派生与泛型基类,但必须要求重复泛型类型或必须指定基类的泛型类型:

    public class Base<T>
    {
        ...
    }
    public class MyClass<T> : Base<T>
    {
        ... 
    }
    或者
    public class MyClass<T> : Base<int>
    {
        ... 
    }

另外,如果指定了泛型基类的数据类型,就可以被泛型类和非泛型类继承,其实道理很简单,当泛型类指定了泛型数据类型时就和普通的类没什么区别了,当然就可以被其他类继承了。

注意:泛型类也可以是抽象类,然后派生其他类。

(4)静态成员

我们知道,非泛型类中的静态数据成员是类所专有的,从这个类创建的所有对象都可以使用它。那么泛型类的静态数据成员就比非泛型类有趣多了。

首先,我们来定义一个泛型类:

    public class Base<T>
    {
        public static int x;
    }

然后我们这样赋值:

    Base<String> .x = 4;
    Base<int> .x = 5;

那么,"x" 的值到底是4还是5呢?对于上面Base<T>的两句赋值语句,"x"的值分别是Base<String >的4和Base<int>的5.,而并不是Base<T>类独有的。也就是说,Base<String >和Base<int>各自都有自己独有的X静态成员,两者之间并不混淆。

  • 泛型接口

上面的链表中,我们使用好几个泛型接口了,在使用泛型接口时个人认为和泛型类用户几乎形同,这里就不再罗嗦了。

  • 泛型方法

将泛型应用与方法,可以得到一些很了不起的效果,下面还以以一个列子说明,就那最简单的两个数据交换函数吧,我们可以这样的定义该泛型方法:

    public void Swap<T>(ref T x, ref T y)
    {
        T tmp = x;
        x = y;
        y = tmp;
    } 

这样,所有的数据类型都可以使用这个函数来交换数据了,是不是很有趣。这就大大减少了C++中函数重载的代码重写过程。在调用泛型时,也很简单:

    int i = 1, j = 0;
    Swap<T>(ref i, ref j);

其实,我们也可以像非泛型方法那样调用泛型方法——因为,C#编译器会通过调用泛型方法来获取参数类型:

    Swap(ref i, ref j);
  • 泛型委托

 

我们知道,委托是类型安全的方法的引用,通过泛型委托,委托的参数可以以后定义,不如.NET库中定义的事件泛型委托:

public sealed delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
            where TEventArgs : EventArgs;

它的第二个参数是TEventArgs泛型类型,并且将该泛型类型添加了约束——就规定了TEventArgs泛型类型必须派生自EventArgs类。

接下来,我们看看一个例子,说明泛型委托的好处:

首先创建一个个人账户信息类(只包含姓名和工薪字段):

    public class Account
    {
        private String name;
        public String Name
        {
            get { return name; }
        }
        private decimal balance;
        public decimal Balance
        {
            get { return balance; }
        }
        public Account(String name, Decimal balance)
        {
            this.name = name;
            this.balance = balance;
        }
    }

然后再创建一个统计所有薪水的类:

    public class Algorithm
    {
        public decimal AccumulateSimple(IEnumerable e)
        {
            decimal sum = 0;
            foreach(Account a in e)
            {
                sum += a.Balance;
            }
            return sum;
        }
    }

主函数中这样的调用:

    class Program
    {
        static void Main()
        {
            List<Account> accounts = new List<Account>();
            accounts.Add(new Account("Name1", 2500));
            accounts.Add(new Account("Name2", 4500));
            accounts.Add(new Account("Name3", 2000));

            Algorithm alg = new Algorithm();
            alg.AccumulateSimple(accounts);
        }
    }

但是,这样我们会发现,这只能统计Account类型的数据了,接下来使用泛型委托灵活的改变统计的范围了。

    public delegate TSummary Action<TInput, TSummary>(TInput t, TSummary s);//定义一个泛型委托

然后再在Algorithm 类中定义一个泛型方法:

    public TSummary Accumulate<TInput, TSummary>(IEnumerable<TInput> coll, Action<TInput, TSummary> action)
        {
            TSummary sum = default(TSummary);
            foreach (TInput input in coll)
            {
                sum = action(input, sum);
            }
            return sum;
        }

最后,我们就可以在Main函数中这样调用了:

            alg.Accumulate<Account, decimal>(accounts, 
                                             delegate(Account a, decimal d) 
                                             {
                                                 return a.Balance + d;
                                             });

上述代码中使用了匿名委托,大家没有忘记吧。

本例代码:

最后:

本文中,有大量了代码,本人个人认为,代码的表现力远远超出了语言所能表达的含义,因为我喜欢代码给我的直观感觉。

好了,下篇该整理集合了,主要是描述列表,队列,栈,链表,有序表,字典,LookUp,HashSet,位数组等数据类型,下回见。