C#泛型

目录

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

泛型并不是一个全新的结构,其他语言中也有类似的概念,比如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,位数组等数据类型,下回见。

你可能感兴趣的:(C#)