zoukankan      html  css  js  c++  java
  • 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++中的const关键字
    用类模板实现容器存储自定义数据类型(类似于STL里面的vector)
    用类模板实现容器存储普通数据类型(类似于STL里面的vector)
    pgmpy包的安装,以及conda的安装
    【SQL语言】SQL语言基础02
    【win7系统】win7系统常遇到的一些问题
    【博客收集】一些关于个人博客、程序员书单、程序资源、学习方法的博文(持续更新中~)!
    【分享】一些好的网站与技术博客
    【ORACLE】Oracle 忘记用户名和密码的和用户被锁定的解决办法
    【ORACLE】SQL Developer 与 PL/SQL Developer与SQL PLUS的使用问题
  • 原文地址:https://www.cnblogs.com/Youhei/p/1736844.html
Copyright © 2011-2022 走看看