android 移除泛型中元素_【自学C#】|| 笔记 22 泛型

49db4879766cc811fa7610cbd3fe7f2c.png

一、泛型简介

    泛型是 C#2.0 推出的新语法,不是语法糖,而是 2.0 由框架升级提供的功能。

    我们在编程程序时,经常会遇到功能非常相似的模块,只是它们处理的数据不一样。

    但我们没有办法,只能分别写多个方法来处理不同的数据类型。

    这个时候,那么问题来了,有没有一种办法,用同一个方法来处理传入不同种类型参数的办法呢?泛型的出现就是专门来解决这个问题的。

    泛型是在 System.Collections.Generic 命名空间中的,用于约束类或方法中的参数类型。

    泛型的应用非常广泛,包括方法、类以及集合等。

    在前面已经介绍了类和方法的定义,那么泛型究竟有什么作用呢?在前面《C#集合》一节中介绍了集合,集合中的项允许是 object 型的值,因此可以存放任意类型的值。

    (总之,就是让一个类似集合的数据,可以存储不同类型的数据。)

    (换句话说,就是把不同类型的数据,存储进一个“集合”中。)

    1.例

        在 ArrayList 中以 double 类型存入学生考试成绩,但存入值时并没有做验证,存入了其他数据类型的值,代码如下。

ArrayList arrayList=new ArrayList();arrayList.Add(100);arrayList.Add("abc");arrayList.Add(85.5);

在输出集合中的元素时,如果使用 double 类型来遍历集合中的元素,代码如下。

foreach (int d in arrayList){    Console.WriteLine(d);}

分析:

    会报错!因为数据内的类型并不统一。

    执行上面的代码,由于在集合中存放的并不全是 double 类型的值,因此会出现 System.InvalidCastException 异常,即指定的转换无效。
    为了避免类似的情况产生,将集合中元素的类型都指定为 double 类型,不能在集合中输入其他类型的值,这种设置方式即为泛型的一种应用。

二、可空类型:Nullable

    对于引用类型的变量来说,如果未对其赋值,在默认情况下是 Null 值。

    对于值类型的变量,如果未赋值,整型变量的默认值为 0。
    但通过 0 判断该变量是否赋值了是不太准确的。
    在 C# 语言中提供了一种泛型类型(即可空类型 (System.Nullable))来解决值类型的变量在未赋值的情况下允许为 Null 的情况。
    定义可空类型变量的语法形式如下。

System.Nullable<T> 变量名;

其中,

    Nullable所在的命名空间 System 在 C# 类文件中默认是直接引入的。

        因此可以省略 System,直接使用 Nullable 即可;

    T 代表任意类型,例如定义一个存放 int 类型值的变量,代码如下。

Nullable<int> a;

    除了使用上面的方法定义可空类型变量以外,还可以通过如下语句定义一个 int 类型的可空类型变量。

int? a

    从上面的定义可以看出,int? 等同于Nullable。

    此外,在使用可空类型时也可以通过 HasValue 属性判断变量值是否为 Null 值。

    1.例

    下面通过实例来演示可空类型的应用。

    分别创建一个 int 的可空类型变量和 double 的可空类型变量,并使用 HasValue 属性判断其值是否为空。

    根据题目要求,代码如下。

class Program{    static void Main(string[] args)    {        int? i = null;        double? d = 3.14;        if (i.HasValue)        {            Console.WriteLine("i 的值为{0}", i);        }        else        {            Console.WriteLine("i 的值为空!");        }        if (d.HasValue)        {            Console.WriteLine("d 的值为{0}", d);        }        else        {            Console.WriteLine("d 的值为空!");        }    }  }

分析:

    定义一个存放 int 类型值的变量i=null。

    定义一个存放 double 类型值的变量d=3.14。

    然后通过 HasValue 属性,判断变量值是否为 Null 值。

运行结果:

b3180d25d4949753ab2ffa9518b80368.png

从上面的执行效果可以看出,可空类型允许将值类型变量的值设置为 Null,并可以通过 HasValue 属性判断其是否为 Null 值。

三、泛型方法的定义及使用

    在 C# 语言中泛型方法是指通过泛型来约束方法中的参数类型,也可以理解为对数据类型设置了参数。如果没有泛型,每次方法中的参数类型都是固定的,不能随意更改。
    在使用泛型后,方法中的数据类型则有指定的泛型来约束,即可以根据提供的泛型来传递不同类型的参数。
    定义泛型方法需要在方法名和参数列表之间加上<>,并在其中使用 T 来代表参数类型。
    当然,也可以使用其他的标识符来代替参数类型, 但通常都使用 T 来表示。下面通过实例来演示泛型方法的使用。

    (总之就是能让参数类型变得灵活。)

    1.例

    创建泛型方法,实现对两个数的求和运算。

class Program{    static void Main(string[] args)    {        //将T设置为double类型        Add<double>(3.3, 4);        //将T设置为int类型        Add<int>(3, 4);    }    //加法运算    private static void Add(T a, T b)    {        double sum = double.Parse(a.ToString()) + double.Parse(b.ToString());        Console.WriteLine(sum);    }}

分析:

    首先是11~15行,自定义的一个泛型方法函数。

        还是个静态的,方便直接调用。

        通过方法可以看到,除了正常的方法名外,多了这个。

        T可以理解成一种“代替传参”,就像数学代数中,不知具体数字的情况下,就用x代替。再或者在不知具体要重复多少次时,人们就喜欢说重复了n次。

        总之,将T理解成一个虚拟的数据类型,并且还是那种随时都能被替换掉的代号,以便使用调用时,改变数据类型。

    方法内部,是一个double类型的数据,并且还通过.Parse()方法,将获取的字符串数据,都强制转换成double类型。

    因为参数是一个字符串,所以还需要通过.ToString()方法,强行转成字符串类型。

    6行和8行

        因为是静态方法,所以可以直接被调用。

        也就将T替换成其他数据类型,并使用。

运行结果:

6351af33a6c072f87b147bd955fcc6ba.png

    从上面的执行效果可以看出,在调用 Add 方法时能指定不同的参数类型执行加法运算。
    如果在调用 Add 方法时,没有按照 中规定的类型传递参数,则会出现编译错误,这样就可以尽量避免程序在运行时出现异常。        

四、泛型类的定义及使用

    C#语言中泛型类的定义与泛型方法类似,是在泛型类的名称后面加上,当然,也可以定义多个类型,即“”。

    表达形式:

        class 类名
        {
            //类的成员
        }

    例

class A{  public T[] items = new T[3];}

    这样,在类的成员中即可使用 T1、T2 等类型来定义。

    1.例

    定义泛型类,并在泛型类中定义数组,提供添加和显示数组中全部元素的 方法。

class MyTest{    private T[] items = new T[3];    private int index = 0;    //向数组中添加项    public void Add(T t){        if (index < 3)        {            items[index] = t;            index++;        }        else        {            Console.WriteLine("数组已满!");        }    }    //读取数组中的全部项    public void Show(){        foreach(T t in items)        {            Console.WriteLine(t);        }    }}

在 Main 方法中调用 MyTest 类中的方法,代码如下。

class Program{    static void Main(string[] args)    {        MyTest<int> test = new MyTest<int>();        test.Add(10);        test.Add(20);        test.Add(30);        test.Show();    }}

分析:

    首先是泛型类MyTest。

        先定义了一个私有变量泛型数组items。

        又定义了一个私有变量index,int类型。

        又定义了一个泛型方法,用于往泛型数组中添加值。

        又定义了一个遍历items的方法。

    然后是调用。

        首先是创建了一个泛型的实例,并把T换成了int类型。

        然后追加数据,就是泛型类种的Add方法。

        最后就是输出打印,就是Show方法。

运行结果:

2f9f5c7993f52c011fa7c38a806e812c.png

    从上面的执行效果可以看出,根据泛型类中指定的数据类型创建数组,并实现了对数组元素的添加和显示。

五、泛型集合定义及使用

    C# 语言中泛型集合是泛型中最常见的应用,主要用于约束集合中存放的元素。
    由于在集合中能存放任意类型的值,在取值时经常会遇到数据类型转换异常的情况,因此推荐在定义集合时使用泛型集合。
    前面《C# ArrayList》与《C# Hashtable》中已经介绍了非泛型集合中的 ArrayList、Hashtable。
    非泛型集合中的 ArrayList、Hashtable 在泛型集合中分别使用 List 和 Dictionary 来表示,其他泛型集合均与非泛型集合一致。

    下面以 List 和 Dictionary 为例介绍泛型集合的使用。

    总之,有是一种新类型,一种新用法。

    1.例

    使用泛型集合 List 实现对学生信息的添加和遍历。

    根据题目要求,将学生信息定义为一个类,并在该类中定义学号、姓名、年龄属性。
    在泛型集合 List 中添加学生信息类的对象,并遍历该集合。实现的代码如下。

using System.Collections.Generic;class Program{    static void Main(string[] args)    {        //定义泛型集合        List list = new List();        //向集合中存入3名学员        list.Add(new Student(1, "小明", 20));        list.Add(new Student(2, "小李", 21));        list.Add(new Student(3, "小赵", 22));        //遍历集合中的元素        foreach(Student stu in list)        {            Console.WriteLine(stu);        }    }}class Student{    //提供有参构造方法,为属性赋值    public Student(int id,string name,int age)    {        this.id = id;        this.name = name;        this.age = age;    }    //学号    public int id { get; set; }    //姓名    public string name { get; set; }    //年龄    public int age { get; set; }    //重写ToString 方法    public override string ToString()    {        return id + ":" + name + ":" + age;    }}

注意:

    需要追加using System.Collections.Generic;!!!

分析:

    首先是第18行~38行。

        自定义了一个Student类,这里跟泛型没关系,正常的类。

        并进行了构造方法,方便创建类的同时,直接赋值。

    然后是第1~17行。

        定义了一个Student数据类型的泛型。

        这是系统已经编辑好的泛型类,并且Add()方法也都集成在了里面。

        所以直接通过Add()方法进行追加数据。

        而这里每个数据,就不再是简单的数字和字符串数据了,而是一个大型的类数据。

        然后就是遍历输出集合中的元素。

运行结果

0697cc2109473ab7fb43211562e2bcd1.png

    从上面的执行效果可以看出,在该泛型集合中存放的是 Student 类的对象,当从集合中取岀元素时并不需要将集合中元素的类型转换为 Student 类的类型,而是直接遍历集合中的元素即可,这也是泛型集合的一个特点。

    总之,因为类的类型各种各样,有了泛型这种方式,明显省去了一一修改参数的数据类型。或者定义相同的功能,但数据类型不同的类。

    2.例

    使用泛型集合 Dictionary 实现学生信息的添加,并能够按照学号查询学生信息。

    根据题目要求,将在实例 1 中所创建学生信息类的对象作为 Dictionary 集合中的 value 值部分,key 值部分使用学生信息类中的学号,这样能很容易地通过学号查询学生的信息。实现的代码如下。

class Program{    static void Main(string[] args)    {        Dictionary<int, Student> dictionary = new Dictionary<int, Student>();        Student stu1 = new Student(1, "小明", 20);        Student stu2 = new Student(2, "小李", 21);        Student stu3 = new Student(3, "小赵", 22);                dictionary.Add(stu1.id, stu1);        dictionary.Add(stu2.id, stu2);        dictionary.Add(stu3.id, stu3);                Console.WriteLine("请输入学号:");        int id = int.Parse(Console.ReadLine());        if (dictionary.ContainsKey(id))        {            Console.WriteLine("学生信息为:{0}", dictionary[id]);        }        else        {            Console.WriteLine("您查找的学号不存在!");        }    }}

分析:

    这是另一种泛型,也就是多参数的泛型。

    系统同样都已经写好,只会导入包后就能直接使用。

    这就是所谓的面向对象编程,听起来很高大上,实际也就这么回事。

    首先是定义了一个int和student两个类型的泛型变量dictionary。

    然后是定义了三个student类的实例,并直接赋值传参。

    再然后就是把student类的实例中的数据调取出来,并往泛型中赋值。

        这里因为有int和student两个数据类型,所以Add()方法中,第一个参数必须是int类型,而第二个参数也必须是student类的数据类型。

运行结果

bb75adc77c75e075458e0c2087e7d920.png

    从上面的执行效果可以看出,根据输入的学号直接从 Dictionary 泛型集合中查询出所对应的学生信息,并且在输出学生信息时不需要进行类型转换,直接输出其对应的 Student 类的对象值即可。

六、IComparable、IComparer接口:比较两个对象的值

    在 C# 语言中提供了 IComparer 和 IComparable 接口比较集合中的对象值,主要用于对集合中的元素排序。 IComparer 接口用于在一个单独的类中实现,用于比较任意两个对象。
    IComparable 接口用于在要比较的对象的类中实现,可以比较任意两个对象。
    在比较器中还提供了泛型接口的表示形式,即 IComparer 和 IComparable 的形式。
    对于 IComparer 接口,方法如下表所示。

方法作用
CompareTo(T obj)比较两个对象值

    如果需要对集合中的元素排序,通常使用 CompareTo 方法实现,下面通过实例来演示 CompareTo 方法的使用。

    1.例

    在上一节《C#泛型集合》中实例 1 的基础上将学生信息按照年龄从大到小输出。
    根据题目要求,如果不使用比较器,由于集合中的元素是 Student 类型的,不能直接排序,需要按照 Student 学生信息类中的年龄属性排序,因此代码比较烦琐。
    使用 CompareTo 方法实现比较简单。在 Student 类中添加 CompareTo 方法,代码如下。

class Student:IComparable<Student>{    //提供有参构造方法,为属性赋值    public Student(int id,string name,int age)    {        this.id = id;        this.name = name;        this.age = age;    }    //学号    public int id { get; set; }    //姓名    public string name { get; set; }    //年龄    public int age { get; set; }    //重写ToString 方法    public override string ToString()    {        return id + ":" + name + ":" + age;    }    //定义比较方法,按照学生的年龄比较    public int CompareTo(Student other)    {        if (this.age > other.age)        {            return -1;        }        return 1;    }}

在 Main 方法中创建泛型集合,并向集合中添加项以及进行排序的代码如下。

class Program{    static void Main(string[] args){        Listlist = new List();        list.Add(new Student(1, "小明", 20));        list.Add(new Student(2, "小李", 21));        list.Add(new Student(3, "小赵", 22));        list.Sort();        foreach(Student stu in list)        {            Console.WriteLine(stu);        }    }}

分析:

    综上有些复杂,所以咱们慢慢捋顺。

    首先是创建一个Student类,并继承IComparable<Student>接口,同时也把T改成了Student,目的是数据类型一点要统一。

    然后就在Student类中进行构造方法函数,就是set和get访问器的那一块。

    重写ToString 方法,是因为系统的顶端早已集成了这个方法,所以可以直接进行重写,不需要重新定义。

    Student类中的第22行:

        定义比较方法,按照学生的年龄比较。

        这是继承自接口里的方法,所以直接添加编写就行。

        而这里面就是用作Student类的年龄比较,可以说复杂的部分都已经被隐藏了起来。

        总之,如果this.age当前的年龄大于other.age,返回的是-1,就是从大到小进行排序。

        反之,如果this.age当前的年龄大于other.age,返回的是1,那就是从小到大进行排序。

        具体原理我也不是很清除,不过通过修改返回值是-1还是1,就能让排序发生变化. 

    字符串类型的值不能直接使用大于、小于的方式比较,要使用字符串的 CompareTo 方法,该方法的返回值是 int 类型,语法形式如下。

字符串1.CompareTo(字符串2);

    当字符串 1 与字符串 2 相等时结果为 0;
    当字符串 1 的字符顺序在字符串 2 前面时结果为 -1;
    当字符串 1 的字符顺序在字符串 2 后面时结果为1。

    然后就是Main()方法中的定义和调用。

        首先是定义Student泛型,并Add()添加数据。

        然后通过Sort()方法进行排序,最后是遍历输出。

 运行结果

3690cb1a3082df9a39efb751d5659160.png

    从上面的执行效果可以看出,在使用集合的 Sort 方法后,集合中的元素是按照学生年龄从大到小的顺序输出的。
    在默认情况下,Sort 方法是将集合中的元素从小到大输出的, 由于在 Student 类中重写了 CompareTo 方法,因此会按照预先定义好的排序规则对学生信息排序。需要说明的是,在 CompareTo 方法中返回值大于 0 则表示第一个对象的值大于第二个对象的值,返回值小于 0 则表示第一个对象的值小于第二个对象的值,返回值等于 0 则表示两个对象的值相等。
    对于实例 1 中的操作也可以使用 IComparer 接口来实现,IComparer 接口中的方法如下表所示。

方法作用
Compare(T obj1,T obj2)比较两个对象值

在使用 IComparer 接口中的 Compare 方法时,需要单独定义一个类来实现该比较方法。

下面通过实例演示 IComparer 接口的使用。

    2.例

    将实例 1 用 IComparer 接口实现。

    根据题目要求,先定义一个比较器的类,再实现对集合中元素的排序。

    代码如下:

class Student    {        //提供有参构造方法,为属性赋值        public Student(int id, string name, int age)        {            this.id = id;            this.name = name;            this.age = age;        }        //学号        public int id { get; set; }        //姓名        public string name { get; set; }        //年龄        public int age { get; set; }        //重写ToString 方法        public override string ToString()        {            return id + ":" + name + ":" + age;        }            }
class MyCompare : IComparer<Student>{    //比较方法    public int Compare(Student x,Student y)    {        if (x.age > y.age)        {            return -1;        }        return 1;    }}

在 Main 方法中应用该比较器对集合中的元素排序,代码如下。

class Program{    static void Main(string[] args){        Listlist = new List();        list.Add(new Student(1, "小明", 20));        list.Add(new Student(2, "小李", 21));        list.Add(new Student(3, "小赵", 22));        //在Sort方法中传递自定义比较器作为参数        list.Sort(new MyCompare);        foreach(Student stu in list)        {            Console.WriteLine(stu);        }    }}

分析:

    首先是一个Student自定义的类,里面没用比较的方法。

    然后又定义了一个继承Student数据类型的泛型接口。

    最后在Main()主函数中创建调用。

    10行,因为返回的不是1就是-1,所以正好满足Sort()方法中的第一个参数,因而也就可以直接使用。

    最后遍历输出。

运行结果

2dff5c183f7281ca2900a137b905cd30.png

    执行上面的代码,效果与实例 1 所示的一致。
    从上面两个实例可以看出,不论使用 IComparer 接口还是 IComparable 接口都能自定义在集合中使用 Sort 方法时的排序。

    提示:

        不仅在泛型集合中允许使用比较器,在非泛型集合中也允许使用比较器,并且可以使用非泛型接口的比较器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值