十九 C# 泛型

1、泛型概述

  • 泛型(Generics)是将类型作为参数传递给类、结构、接口和方法,这些参数通常被称作站位符,是类、就结构、接口和方法所存储或使用的一个或多个类型的占位符。
  • 泛型集合是可以将类型参数用作它所存储的对象的类型的占位符:类型参数作为其字段的类型和其方法的参数类型出现。
  • 泛型方法可以将其类型参数用作其返回值的类型或者其参数的类型之一。
  • 泛型为开发者提供了一种高性能的编程方式,能够提高代码的重用性,并允许开发者别写非常有效的解决防范。
  • 使用泛型可以提高性能,其中显著的一个改进是.NET框架组件不会在值类型上使用装箱(boxing);使用泛型类的另一个令人经验的特性是可以跟踪抢数据类型定义。
  • 泛型和泛型方法同时具备可重用性、类型安全和效率高等特点,通常用在集合和集合上运行的方法中。
using System;
using System.Collections.Generic;   //泛型集合
using System.Linq;

namespace _1_泛型概述
{
    class Program
    {

        static void Main(string[] args)
        {
            //集合与数组①集合长度可以改变②集合添加数据时,不用考虑数据类型(集合中添加数据自动转换为object类型)③在使用具体元素是需要将元素转换为想要的数据类型
            List<int> list = new List<int>(); //尖括号中代表类型(T是类型的总称呼 Type,)整型类型的泛型
            list.Add(8);
            list.Add(7);
            list.AddRange(new int[] { 1, 2, 3, 4, 5, 6 });  //向泛型集合中添加数组
            list.AddRange(list);                            //将自己当做元素添加

            list.Reverse(); //倒序
            list.Sort();  //排序
            list.Insert(3, 100);  //在索引值为3的地方插入100;

            for (int i = 0; i < list.Count; i++)
                Console.WriteLine(list[i]);
            int[] nums = list.ToArray();  //将泛型转换为数组
            List<int> List2 = nums.ToList();  //将数组转换为泛型集合
            Console.ReadKey();
                

        }
    }
}

2、泛型集合类使用场合概述

在System.Collections.Generic的命名空间中定义了许多泛型接口和集合类

 - List<T>: 用法类似于System.Collections下的ArrayList类,可以动态增大后减小容量,他实现了IList<T> ICollection<T>接口。这个类还支持掐功能;例如:排序,倒序;删除,增加元素等。
 - Dictionary<TKey,TValue>:键值对集合类,是在存储键值对的集合类,实现了IDictionary<TKey,TValue> ICollection<KeyValuePair<Tkey,TValue>>  ISerializable IDeserialzationCallBack接口
 - SortedList<Tkey,TValue>:实现了IDictionary<TKey,TValue> ICollection<KeyValuePir<TKey,TValue>>接口,类似于Dictionary<TKey,TValue>,此集合类可以按照键来排序
 - LinkedList<T>:此集合是双向链表,实现了ICollection<T> IEnumerable<T> ISerializble IDeserializationCallBack接口
 - Queue<T>:先进先出,在此集合类中先添加的元素会被先读取出,这类似我们打印队列实现的接口有ICollection<T>和IEnumerable<T>接口,还有方法:
	 - ①Enque在队列的尾部添加对应的对象  
	 - ②Deque:返回队列开头的对象并删除该对象。
	 - ③Peek:可以在队列中读取第一个对象,但不删除该对象
 - Stack<T> :堆栈特性先进后出组合,他实现了ICollection<T> IEnumerable<T> 还有方法
	 - Push:可以把一个对象添加到堆栈的尾部
	 - Pop:读取堆栈尾部的对象,并将其删除
	 - Peek:读取堆栈尾部的对象,但不删除

3、自定义泛型类的创建

  • System.Collection.Generic的命名空间自带的泛型类都含有一个参数T, 并且这个参数是在一堆分隔符"<>"中
  • 要创建自定义泛型类,与一般的类的定义很相似,只是多了类型参数<T>,创建自定义类型和方法,可以提供个人的通用解决方案和设计类型安全高效模式等。
  • 在创建泛型类的实例时,会指定实际类型来替换泛型参数,这会建立一个新的泛型类,称为构造泛型类,选定的类型将替换所有出现的类型参数,最后得到按照选择的类型定制的类型安全的类
  • 泛型方法可以出现在泛型或非泛型类中。需要注意的是,只有当方法具有自己的类型参数列表时,才能称其为泛型方法
using System;

namespace 自定义泛型类的创建
{
    public class TGeneric<T>  //泛型类一般以T开头,类似接口一般用I开头
    {
        //可以在泛型类中定义字段、属性、方法等
        public T Field;  //泛型字段

    }
    class commonlyClass
    {
        public T G<T>(T t) // 普通方法,  G<T>(T t) ,没有自己的类型参数里列表
        {
            return t;
        }
    }
    class TGeneric2<T>
    {
        public T M(T t) //泛型方法, 有自己的类型参数列表  
        {
            return t;

        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            TGeneric<string> g = new TGeneric<string>();
            g.Field = "darly";
            Console.WriteLine(g.Field);
            Console.WriteLine(g.Field.GetType());
            Console.ReadKey();
        }
    }
}

类型参数<T>

  • 类型参数T是用来定义泛型类时的占位符,T并不是一种类型,它仅代表了某种可能的类型。在定义是,T出现的位置可在使用时用任何类型来替换。
using System;

namespace _4_类型参数T
{
    //定义泛型类
    public class TGenericdemon<T>
    {
        public void Exp(T input)
        {
            Console.WriteLine(input);
        }
    }
 
    
    class Program
    {
        private class customClass  //自定义类别
        {

        }
        static void Main(string[] args)
        {
            //实例化泛型类 gd1,char
            TGenericdemon<char> gd1 = new TGenericdemon<char>();
            gd1.Exp('男');
            Console.WriteLine("实例化类型为char的TGenericdemon的gd1成功");
            //实例化泛型类 gd2,int
            TGenericdemon<int> gd2 = new TGenericdemon<int>();
            gd2.Exp(2);
            Console.WriteLine("实例化类型为int的TGenericdemon的gd2成功");

            //实例化泛型类 gd3,string
            TGenericdemon<string> gd3 = new TGenericdemon<string>();
            gd3.Exp("darly");
            Console.WriteLine("实例化类型为String的TGenericdemon的gd3成功");

            //实例化泛型了 gd4 自定义的类型
            TGenericdemon<customClass> gd4 = new TGenericdemon<customClass>();
            //gd4.Exp("darly");
            Console.WriteLine("实例化类型为自定义类型的TGenericdemon的gd4成功");
            Console.ReadKey();
        }
    }
}

5、泛型继承之开放与封闭

泛型类是抽象类而非具体的数据类型,那么它是否符合普通的继承规则?答案是肯定的。不同的是在泛型类继承上上,基类和派生类可能只有一个泛型类,也可能二者都是,这样就会出现三种情况

基类派生类
普通类泛型类
泛型类普通类
泛型类泛型类

5.1 开放与封闭

  • 在C#语言中,所有的类型可分为开放类型和封闭类型两部分
  • 开放类型是指有类型参数的类型,它包括:
    • ①类型参数本身,类型参数就是指的
    • ②以开放类型为元素类型的数组类型
    • ③开放类型的构造类型
  • 封闭类是指开放类以外的所有类型
  • 当把一个开放类型中所包含的类型参数都替换成封闭类型时,该类型就成了一个封闭类型。但是这里要说明的一点是,只有封闭类型才可以创建实例,并拥有内存存储,而开放类型不是真正的数据类型。
  • 例如:
    • GenericDemo<T>, 由于含有参数<T>所以为开放类型。
    • 实例化后GenericDemon<int> 则变为封闭类型,
    • 实例化为GenericDemon<String> 同样为封闭类型

6、泛型继承之泛型类继承普通类

  • 普通类继承泛型类和类继承是一样的
    • ①派生的泛型类可以从普通基类中继承各种成员
    • ②隐藏基类中的同名成员
    • ③通过重载来实现多态性
  • 至于的泛型类是如何在自己的其他成员中使用类型参数来实现泛型的功能,这和基类没有任何关系,唯一注意的一点是:不能用派生的泛型类中任何涉及道德成员来重载基类中的成员
using System;

namespace _6_普通类继承泛型类
{
    //泛型类继承普通类,并不是普通类继承泛型类
    public abstract class baseClass        //创建一个普通类作为基类(父类)
    {
        protected int user_name; //定义字段
        public virtual int user_Name   //定义属性
        {
            set { user_name = value; }
            get { return user_name; }
        }
        public baseClass(int index)   //构造函数   构造函数没有返回值,连void也不能写,必须public;构造函数的名称必须跟类名一样
        {
            user_name = index;
        }
        public abstract void method(int t);  //抽象方法 抽象方法不允许任何实现体,包括不允许有{}
    }
    public class GenClass<T> : baseClass      //泛型类继承普通类。<T>起到将类型参数传递给泛型的方式,代表是泛型类
    {
        protected new T user_name;    //隐藏基类中的同名成员(隐藏字段);相当于重新定义(new关键字)了基类中的user_name字段类型,从而达到隐藏基类中字段的作用。
        public new T user_Name        //隐藏属性
        {
            get { return user_name; }
        }
        public GenClass(int index) : base(index)  //构造函数可以有多个,此处为构造函数1
        { }
        public GenClass(T t) : base(1000)        //构造函数可以有多个,此处为构造函数2  ,此处为什么直接写1000,参考下面链接
        {
            user_name = t;
        }
        public override void method(int t)       //重写普通基类中的抽象方法
        {
            Console.WriteLine("调用方法成功");
        }
        public void method(T t)
        { }

    }
    class Program
    {
        static void Main(string[] args)
        {
            int val = 1000;
            GenClass<int> g = new GenClass<int>(val);
            g.method(g.user_Name);
            Console.WriteLine("泛型继承成功:泛型类继承普通类");
            Console.ReadKey();
        }
    }
}

C#中同一个类的多个构造方法之间的调用

7、普通类继承泛型类

由于基类和培生累都是封闭类型,基类和派生类的类型可直接确定。(含有类型参数T的为开放类型,以开放类型为元素的数组类型,开放类型的构造类型都是开放类型(见本文5.1),初次以外都是封闭类型)

using System;

namespace 普通类继承泛型类
{
    //定义一个泛型的基类
    abstract class genClass<T>
    {
        protected T field;    //定义一个泛型字段
        public virtual T Field   //定义一个泛型属性
        {
            get { return field; }
        }
        public genClass(int index) { }  //构造方法
        public genClass(T t) { field = t; }        //构造方法

        public abstract void method(T t);
    }

    //定义一个普通类,并继承泛型类
    class ordinaryClass:genClass<int>
    {
        public override int Field  // 属性重载
        {
            get
            {
                return base.Field;
            }
        }
        public ordinaryClass(int t) : base(t) { }  //构造函数,构造函数中t需要从基类中获取,类似于继承

        public override void method(int t)   //重写基类中的抽象方法
        {
            Console.WriteLine("field属性值为:{0}",t);
        }


    }
    class Program
    {   

        static void Main(string[] args)
        {
            int val = 1000;
            ordinaryClass oc = new ordinaryClass(val);
            oc.method(val);
            Console.WriteLine("普通类继承泛型类成功");
            Console.ReadKey();

        }
    }
}

在这里插入图片描述

泛型类继承泛型类

这是最为负责的一种,在泛型类与泛型类之间的继承应当注意如下:

  • ①在定义一个泛型类之后,它自身或他的某个构造类型都可能称为基类。
  • ②如果泛型定义了多个参数,除了泛型类本身,其封闭的构造类型和开放的构造类型都可以作为其他泛型类的基类。
  • ③基类中如果出现了类型参数,那么所在这些类型参数都必须在派生类的定义中出现
  • ④如果基类和派生类中同时出现了某个类型参数,那么二者的含义是一致的,他们只带同一个开放类型,而最终在使用时会被同一个封闭类型取代。
using System;
using System.Collections.Generic;  //泛型类的命名空间
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _8_泛型类继承泛型类
{
    //定义泛型基类,多个参数
    // <T>T可以是任意的标识符,只要遵循我们的通常C#命名规则即可,例如多个参数T1,T2,T3等
    abstract class genBaseClass<T1, T2>   //定义了两个参数的泛型类
    {
        public T1[] str;   //定义一个泛型数组
        public T2[] ind;
        //定义一个索引属性
        public T1 this[int index]
        {
            set { str[index] = value; }
            get { return str[index]; }

        }
        //定义一个获取数组长度的属性
        public int length
        {
            get { return str.Length; }
        }
        // 构造函数
        public genBaseClass(int index)
        {
            str = new T1[index];
            ind = new T2[index];
        }
    }

    //定义派生的泛型类
    class genClass<T>: genBaseClass<T, int>   //如果基类和派生类中同时出现了某个类型参数,那么二者的含义是一致的,他们只带同一个开放类型,而最终在使用时会被同一个封闭类型取代。
    {

        public genClass(int index):base(index)  //如果泛型定义了多个参数,除了泛型类本身,其封闭的构造类型和开放的构造类型都可以作为其他泛型类的基类。(继承构造类型)
        {
            for (int i = 0; i <length ; i++)
            {
                ind[i] = i;
            }
        }
        //派生类中定义一个output方法
        public void output()
        {
            for(int i=0; i<length; i++)
            {
                Console.WriteLine(str[i].ToString());
            }
        }
    }

    class Program
    {
        

        static void Main(string[] args)
        {
            //实例化类对象
            genClass<String> gc = new genClass<string>(6);
            //添加元素的过程
            gc[0] = "first";
            gc[1] = "Second";
            gc[2] = "third";
            gc[3] = "fourth";
            gc[4] = "fifth";
            gc[5] = "sixth";
            //调用方法,遍历元素
            gc.output();
            Console.WriteLine("泛型类继承泛型类成功");
            Console.ReadLine();

        }
    }
}

9、泛型集合接口概述

System.Collections.Generics下面定义了一下7个接口

  • ICollection<T>
  • IList<T>
  • IEnumerable<T>
  • IEnumerator<T>
  • IDictionary<Tkey, Tvalue>
  • IComparer<T>
  • IEqualityComparer<T>

ICollection<T>只能连续第插入或删除一些元素
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10 泛型接口的使用

  • 使用泛型可以定义接口,接口中的方法可以带泛型参数,泛型接口为拍讯和比较以及由泛型集合类型共享的功能提供了与非泛型接口对应的类型安全的泛型接口。泛型皆苦比普通接口更为抽象的数据类型.可以指定一个或多个类型参数,也可以为类型参数添加类型限制.而泛型接口本身也可用于类型限制.
  • 和泛型类类似,在类型限制的定义中,要求用于限制的接口要么是封闭类型(及不含类型参数),妖魔所含的类型参数在所定义的泛型接口的类型参数列表中出现
  • 接口是抽象数据类型,它只可以有方法成员,而且之定义方法的标识而不提供方法的实现,试用期来较为简单.泛型接口所定义的类型参数可以在其方法成员中作为参数或返回类型来使用.作为方法参数使用时,还可以是应用参数、输出参数、和数组参数。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _10_泛型接口的使用
{
    //普通的接口
    public interface IMyinterface { }
    //带有一个参数的泛型接口
    public interface IMyinterface<T> { }
    //带两个参数的泛型接口
    public interface IMyinterface<T1, T2> { }


    public interface IRelation<T1,T2>
    {
        int Forward(T1 forward);  //方法只提供标识,不提供实现体
        int After(T2 after);

        T1 GetForware(int index);//泛型接口所定义的类型参数可以在其方法成员中作为参数或返回类型来使用(此命令行定义的方法返回的参数类型为T1)

        void GetAfter(int index, out T2 after);  //第一个参数为整型参数,第二个参数为输入类型的泛型参数(out使用见下面链接)
        void change(ref T1 forward, ref T2 after);   //关于ref的使用,见下面链接
    }

    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

八 C#方法

11 定义泛型方法

  • 在有些情况下,一个类型中可能只有少数的方法成员用到了类型参数,这是就未必需要将整个类型定义称为泛型类,这就引入了泛型方法。
  • 在C#的泛型机制中,只支持在方法声明上包含类型参数,称为泛型方法。特别注意的是,泛型不支持在除了方法以外的其他类/接口成员上使用类型参数,但这些成员可以被包含在泛型类中,并且可以使用泛型类的类型参数。还有一点需要说的就是,泛型方法可以在泛型类型中,也可以存在于非非泛型类型中。
  • 和泛型类型的定义类似,定义泛型方法也是在方法名之后将类型参数包含在一堆“<>”中.如果有多个类型参数则相互间用都好分割.之后,所定义的类型参数及可以作为方法的参数类型和返回类型,也可以用来在方法的执行代码中定义局部变量.除了类型参数之外,泛型方法的定义规则与普通方法的定义规则基本相同.
  • 泛型方法既可以属于普通类型,也可以属于泛型类型(包括泛型类,泛型接口和泛型接口)但在C#中不允许定义泛型的属性\事件\索引函数和操作符。

在这里插入图片描述
在这里插入图片描述

namespace _11_定义泛型方法
{
    //定义普通类
    public class myclass
    {
        //定义泛型方法
        public bool Contains<T>(T[] TArry, T TValue)   //此方法用于判定数组中是否包含某个元素,所以定义返回bool类型的泛型方法
        {
            //遍历元素
            foreach(T t in TArry)    //也可以用来在方法的执行代码中定义局部变量
            {
                //判断是否存在TValue
                if (t.Equals(TValue))
                    return true;  //存在返回true
            }
            return false;  //不存在返回false
        }

        //所定义的类型参数及可以作为方法的参数类型和返回类型,
        public T Contains<T,T1,T2,A>(T[] TArry, T TValue)   //此方法用于判定数组中是否包含某个元素,所以定义返回bool类型的泛型方法
        {
            //此处只是说明多个类型参数,且类型参数不一定要用T表示,也可以用a,b,cd等字符标识,实现体没有任何意义
            T mytypevalue = TValue;
            return mytypevalue;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

调用泛型方法

在程序中调用泛型方法有两种方式

  • 完整调用:在调用时显示指定方法的类型参数列表。显示调用泛型方法的优点在子程序清晰(可读性高),并且能够在编译时就判断出取代类型参数的封闭类型
  • 简写调用:在调用时省略方法的类型参数列表,书写格式和调用普通方法相同。
  • 由于传递给泛型方法的两个参数类型分别是int[]和int,调用过程和结果与完成调用一样的
  • 在采用简写调用方式是,编译器需要检查每个参数类型,并视图由此推断出每个类型参数最终被那个封闭类型所取代,这个构成为类型推断。如果类型推断成功,则会将简写调用代码映射到对应的完整调用代码;否则编译器就会失败
  • 编译器进行类型推断的算法较为复杂,这里不进行详细讲解,而是给出一个简单的判断方法,即泛型方法的声明中出现相同的类型参数的地方,调用时必须以相同的封闭类型去替换这些类型参数,否则类型推断就会失败。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _11_泛型方法的调用
{
    public class myclass
    {
        //泛型方法实现,数组中是否包含某一元素
        public bool Contains<T>(T[] TArray, T TValue)
        {
            foreach(T t in TArray)
            {
                if (t.Equals(TValue)) return true;
            }
            return false;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            
            int[] myint = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            myclass mc = new myclass();
            bool mybool;
            int testInt = 50;
            //完整调用
            Console.WriteLine("完成调用");
            mybool = mc.Contains<int>(myint, testInt);
            if (mybool) Console.WriteLine("数组中包含有元素" + testInt);
            else Console.WriteLine("数组中不包含元素" + testInt);
            Console.ReadKey();

            //简写调用
            Console.WriteLine("简写调用");
            bool mybool2;
            mybool2 = mc.Contains(myint, testInt);
            if (mybool2) Console.WriteLine("数组中包含有元素" + testInt);
            else Console.WriteLine("数组中不包含元素" + testInt);
            Console.ReadKey();

        }
    }
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值