html5手机网站开发工具,陕西网站建设咨询,定制彩票网站开发,邢台做移动网站价格表一、为什么使用泛型#xff1f;泛型其实就是一个不确定的类型#xff0c;可以用在类和方法上#xff0c;泛型在声明期间没有明确的定义类型#xff0c;编译完成之后会生成一个占位符#xff0c;只有在调用者调用时#xff0c;传入指定的类型#xff0c;才会用确切的类型… 一、为什么使用泛型泛型其实就是一个不确定的类型可以用在类和方法上泛型在声明期间没有明确的定义类型编译完成之后会生成一个占位符只有在调用者调用时传入指定的类型才会用确切的类型将占位符替换掉。首先我们要明白泛型是泛型集合是集合泛型集合就是带泛型的集合。下面我们来模仿这List集合看一下下面这个例子我们的目的是要写一个可以存放任何动物的集合首先抽象出一个动物类//动物类public class Animal{//随便定义出一个属性和方法public String SkinColor { get; set; }//皮肤颜色//会跑的方法public virtual void CanRun() { Console.WriteLine(Animal Run Can); }}然后创建Dog类和Pig类//动物子类 Dogpublic class Dog : Animal{//重写父类方法public override void CanRun() { Console.WriteLine(Dog Can Run); }}//动物子类 Pigpublic class Pig : Animal{//重写父类方法public override void CanRun() { Console.WriteLine(Pig Can Run); }}因为我们的目的是存放所有的动物然后我们来写一个AnimalHouse用来存放所有动物//存放所有动物public class AnimalHouse{//由于自己写线性表需要考虑很多东西而且我们是要讲泛型的所以内部就用List来实现private ListAnimal animal new ListAnimal();//添加方法public void AddAnimal(Animal a) { animal.Add(a); }//移除方法并返回是否成功public bool RemoveAnimal(Animal a) {return animal.Remove(a); }}AnimalHouse类型可以存放所有的动物存放起来很方便。但是每次取出的话使用起来会很不方便因为只能用一些动物的特征而无法使用子类的特征例如Dog子类有CanSwim()方法会游泳的方法而动物中是没有这个方法的所以就无法进行调用必须将Animal类型转换为Dog类型才可以使用不仅会增加额外的开销而且还有很大的不确定性可能转换失败因为AnimalHouse中是存放了很多种动物子类。如果我们有方法可以做到让调用者来决定添加什么类型(具体的类型例如Dog、Pig)然后我们创建什么类型是不是这些问题就不存在了泛型就可以做到。我们看一下泛型是如何定义的//用在类中public class ClassNameCName{//用在方法中public void MothedMName() { }//泛型类中具体使用CName//返回值为CName并且接受一个类型为CName类型的对象public CName GetC(CName c) {//default关键字的作用就是返回类型的默认值return default(CName); }}其中CName和MName是可变的类型(名字也是可变的)用法的话就和类型用法一样用的时候就把它当成具体的类型来用。了解过泛型接下来我们使用泛型把AnimalHouse类更改一下将所有类型Animal更改为泛型如下public class AnimalHouseT{private ListT animal new ListT();public void AddAnimal(T a) { animal.Add(a); }public bool RemoveAnimal(T a) {return animal.Remove(a); }}AnimalHouse类型想要存储什么样的动物就可以完全交由调用者来决定//声明存放所有Dog类型的集合AnimalHouseDog dog new AnimalHouseDog();//声明存放所有Pig类型的集合AnimalHousePig pig new AnimalHousePig();调用方法的时候原本写的是T类型当声明的时候传入具体的类型之后类中所有的T都会变成具体的类型例如Dog类型Pig类型 这样我们的问题就解决了当调用者传入什么类型我们就构造什么类型的集合来存放动物。但是还有一个问题就是调用者也可以不传入动物调用者可以传入一个桌子(Desk类)、电脑(Computer)但是这些都不是我们想要的。比如我们需要调用动物的CanRun方法让动物跑一下再放入集合里z因为我们知道动物都是继承自Animal类所有动物都会有CanRun方法但是如果传入过来一个飞Desk类我们还能使用CanRun方法吗答案是未知的所以为了确保安全我们需要对传入的类型进行约束。二、泛型约束泛型约束就是对泛型(传入的类型)进行约束约束就是指定该类型必须满足某些特定的特征例如可以被实例化、比如实现Animal类等等我们来看一下官方文档上都有那些泛型约束约束说明where T : struct类型参数必须是值类型。 可以指定除 NullableT 以外的任何值类型。 有关可以为 null 的类型的详细信息请参阅可以为 null 的类型。where T : class类型参数必须是引用类型。 此约束还应用于任何类、接口、委托或数组类型。where T : unmanaged类型参数必须是非托管类型。where T : new()类型参数必须具有公共无参数构造函数。 与其他约束一起使用时new() 约束必须最后指定。where T : 基类名类型参数必须是指定的基类或派生自指定的基类。where T : 接口名称类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。where T : U为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。对多个参数应用约束//微软官方例子class Base { }class TestT, Uwhere U : structwhere T : Base, new(){ }使用的话只需要在泛型后面添加 where 泛型 : 泛型约束1、泛型约束2....如果有new()约束的话则必须放在最后说明都有很详细的介绍。然后我们来为AnimalHouse添加泛型约束为必须包含公共无参构造函数和基类必须是Animal//Animal约束T必须是Animal的子类或者本身new()约束放在最后public class AnimalHouseT where T : Animal, new(){private ListT animal new ListT();public void AddAnimal(T a) {//调用CanRun方法//如果不加Animal泛型约束是无法调用.CanRun方法的因为类型是不确定的 a.CanRun();//添加 animal.Add(a); }public bool RemoveAnimal(T a) {return animal.Remove(a); }}然后调用的时候我们传入Object试一下提示Object类型不能传入AnimalHouseT中因为无法转换为Animal类型。我们在写一个继承Animal类的Tiger子类然后私有化构造函数//动物子类 Tigerpublic class Tiger : Animal{//私有化构造函数private Tiger() { }public override void CanRun() { Console.WriteLine(Tiger Can Run); }}然后创建AnimalHouse类型对象传入Tiger类试一下提示必须是公共无参的非抽象类型构造函数。现在我们的AnimalHouse类就很完善了可以存入所有的动物而且只能存入动物三、逆变和协变先来看一个问题Dog dog new Dog();Animal animal dog;这样写编译是不会报错的因为Dog继承了Animal默认会进行一个隐式转换但是下面这样写AnimalHouseDog dogHouse new AnimalHouseDog();AnimalHouseAnimal animalHouse dogHouse;这样写的话会报一个无法转换类型的错误。强转的话会转换失败我们设个断点在后一句然后监视一下animalHouse的值可以看到值为null//强转编译会通过强转的话会转换失败值为nullIAnimalHouseAnimal animalHouse dogHouse as IAnimalHouseAnimal;协变就是为了解决这一问题的这样做其实也是为了解决类型安全问题百度百科例如类型安全代码不能从其他对象的私有字段读取值。它只从定义完善的允许方式访问类型才能读取。因为协变只能用在接口或者委托类型中所以我们将AnimalHouse抽象抽来一个空接口IAnimalHouse然后实现该接口//动物房子接口所有动物的房子必须继承该接口例如红砖动物房子别墅动物房public interface IAnimalHouseT where T : Animal,new(){}//实现IAnimalHouse接口public class AnimalHouseT : IAnimalHouseT where T : Animal,new(){private ListT animal new ListT();public void AddAnimal(T a) { a.CanRun(); animal.Add(a); }public bool RemoveAnimal(T a) {return animal.Remove(a); }}协变是在T泛型前使用out关键字其他不需要做修改public interface IAnimalHouseout T where T : Animal,new(){}接下来我们用接口来调用一下现在一切ok了编译也可以通过IAnimalHouseDog dogHouse new AnimalHouseDog();IAnimalHouseAnimal animalHouse dogHouse;协变的作用就是可以将子类泛型隐式转换为父类泛型而逆变就是将父类泛型隐式转换为子类泛型将接口类型改为使用in关键字public interface IAnimalHousein T where T : Animal,new(){}逆变就完成了IAnimalHouseAnimal animalHouse new AnimalHouseAnimal();IAnimalHouseDog dogHouse animalHouse;逆变和协变还有两点协变时泛型无法作为参数、逆变时泛型无法作为返回值。逆变协变语法都是一些 非常粗糙的东西重要的是思想、思想、思想。然后我们来看一下为什么要有逆变和协变什么叫做类型安全C#中的类型安全个人理解大致就是一个对象向父类转换时会隐式安全的转换而两种不确定可以成功转换的类型父类转子类转换时必须显式转换。解决了类型安全大致就是这两种类型一定可以转换成功。如果有错误欢迎指正。协变的话我相信应该很好理解将子类转换为父类兼容性好解决了类型安全因为子类转父类是肯定可以转换成功的而协变作为返回值是百分百的类型安全“逆变为什么又是解决了类型安全呢子类转父类也安全吗不是有可能存在失败吗”其实逆变的内部也是实现子类转换为父类所以说也是安全的。“可是我明明看到的是IAnimalHouseDog dogHouse animalHouse;将父类对象赋值给了子类你还想骗人”这样写确实是将父类转换为子类不过逆变是用在作为参数传递的。这是因为写代码的“视角”原因为什么协变这么好理解因为子类转换父类很明显可一看出来“IAnimalHouseAnimal animalHouse dogHouse;”然后我们换个“视角”将逆变作为参数传递一下看这个例子先将IAnimalHouse接口修改一下public interface IAnimalHousein T where T : Animal,new(){//添加方法void AddAnimal(T a);//移除方法bool RemoveAnimal(T a);}然后我们在主类Main函数所在的类中添加一个TestIn方法来说明为什么逆变是安全的//需要一个IAnimalHouseDog类型的参数public void TestIn(IAnimalHouseDog dog) {}接下来我们将“视角”切到TestIn中作为第一视角我们正在写这个方法至于其他人如何调用我们都是不得而知的我们就随便在当前方法中添加一个操作为dog变量添加一个Dog对象TestIn方法改为如下//需要一个IAnimalHouseDog类型的参数public static void TestIn(IAnimalHouseDog dog) { Dog d new Dog(); dog.AddAnimal(d);}我们将“视角”调用者视角如果我们想调用当前方法只有两种方法//第一种AnimalHouseDog dogHouse new AnimalHouseDog();TestIn(dogHouse);//第二种AnimalHouseAnimal animalHouse new AnimalHouseAnimal();//因为使用了in关键字所以可以传入父类对象TestIn(animalHouse);第一种的话我们就不看了很正常也很合理我们主要来看第二种那第二种类型安全又在哪儿呢可能有人已经反应过来了我们再来看一下TestIn方法有一个需要传递过来的IAnimalHouseDog类型的dog对象如果调用者是使用第二种方法调用的那这个所谓的IAnimalHouseDog类型的dog对象是不是其实就是AnimalHouseAnimal类型的对象而dog.AddAnimal(参数类型);的参数类型是不是就是需要一个Animal类型的对象那传入一个Dog类型的d对象是不是最终也是转换为Animal类型放入dog对象中所以当逆变作为参数传递时类型是安全的。思考那么现在你能明白上面那个错误为什么“协变时泛型无法作为参数、逆变时泛型无法作为返回值”了吗逆变思考答案建议自己认真思考过后再看协变思考答案建议自己认真思考过后再看原文链接https://www.cnblogs.com/ckka/p/11395777.html.NET社区新闻深度好文欢迎访问公众号文章汇总 http://www.csharpkit.com