一、没有泛型之前

在没有泛型之前,我们是怎么处理不同类型的相同操作的:

  1. 示例1
  2. //下面是一个处理string类型的集合类型
  3. public class MyStringList
  4. {
  5. string[] _list;
  6. public void Add(string x)
  7. {
  8. //将x添加到_list中,省略实现
  9. }
  10. public string this[int index]
  11. {
  12. get { return _list[index]; }
  13. }
  14. }
  15. //调用
  16. MyStringList myStringList = new MyStringList();
  17. myStringList.Add("abc");
  18. var str = myStringList[0];
  1. 示例2
  2. //如果我们需要处理int类型就需要复制粘贴然后把string类型替换为int类型:
  3. public class MyIntList
  4. {
  5. int [] _list;
  6. public void Add(int x)
  7. {
  8. //将x添加到_list中,省略实现
  9. }
  10. public int this[int index]
  11. {
  12. get { return _list[index]; }
  13. }
  14. }
  15. //调用
  16. MyIntList myIntList = new MyIntList();
  17. myIntList.Add(100);
  18. var num = myIntList[0];

可以看得出我们的代码大部分是重复的,而作为有追求的程序员是不允许发生这样的事情的。

于是乎,我们做了如下改变:

  1. 示例3
  2. public class MyObjList
  3. {
  4. object[] _list;
  5. public void Add(object x)
  6. {
  7. //将x添加到_list中,省略实现
  8. }
  9. public object this[int index]
  10. {
  11. get { return _list[index]; }
  12. }
  13. }
  14. //调用
  15. MyObjList myObjList = new MyObjList();
  16. myObjList.Add(100);
  17. var num = (int)myObjList[0];

从上面这三段代码中,我们可以看出一些问题:

  1. int和string集合类型的代码大量重复(维护难度大)。
  2. object集合类型发生了装箱和拆箱(损耗性能)。
  3. object集合类型是存在安全隐患的(类型不安全)。

问题1,虽然代码重复但是没有装箱、拆箱而且类型是安全的

问题2,发生了装箱和拆箱,是损耗性能影响执行效率的。

问题3,如果add的类型不是int类型,在编译器是不会检查出来的(编译通过),运行期就会报错,MyObjList类似于我们熟知的ArrayList

现在,我们必须解决如下问题

1、避免代码重复

2、避免装箱和拆箱

3、保证类型安全

范型为我们提供了完美的解决方案

二、什么是泛型

如果你理解类是对象的模板(类是具有相同属性和行为的对象的抽象),那么泛型就很好理解了。

泛型:generic paradigm(通用的范式),generic这个单词也很好的说明了模板这个概念:通用的,标准的。

泛型是类型的模板

不同的是:作为模板的类是通过实例化产生不同的对象,而泛型是通过不同的类型实参产生不同的类型

泛型的基本概念介绍完,我们来看看泛型到底是怎么帮我们解决问题的

  如何解决代码重复:提取代码相同的部分,封装变换的部分——封装变化,而示例1和示例2中变换的部分就是int和string类型本身,如何将类型抽象呢

  1. 示例4
  2. //将示例3改装下
  3. public class MyList<T>
  4. {
  5. T [] _list;
  6. public void Add(T x)
  7. {
  8. //将x添加到_list中,省略实现
  9. }
  10. public T this[int index]
  11. {
  12. get { return _list[index]; }
  13. }
  14. }

类型参数 T

类型参数可以理解为泛型的"形参"("形参"一般用来形容方法的),有“形参”就会有实参。如我们声明的List



通过类型参数解决了代码重复的问题

  如何解决装箱、拆箱以及类型安全的问题:

  1. //示例5
  2. List<int> list = new List<int>();
  3. list.Add(100);//强类型无需装箱
  4. //list.Add("ABC"); 编译期安全检查报错
  5. int num = list[0];//无需拆箱

声明泛型类型时,因为确定了类型实参,所以操作泛型类型不需要装箱、拆箱,而且泛型将大量安全检查从运行时转移到了编译时进行,保证了类型安全。

注:C#为我们提供了5种泛型:类、结构、接口、委托和方法。

在示例4中,自定义泛型集合只是添加和获取类型参数的实例,除此之外,没有对类型参数实例的成员做任何操作。C#的所有类型都继承自Object类型,也就是说,我们目前只能操作Object中的成员(Equals,GetType,ToString等)。但是,我自定义的泛型很多时候是需要操作类型更多的成员

新需求,打印员工的信息

  1. 示例6
  2. public class Person
  3. {
  4. public string Name { get; set; }
  5. public int Age{ get; set; }
  6. }
  7. public class Employee : Person { }
  8. public class PrintEmployeeInfo<T>
  9. {
  10. public void Print(T t)
  11. {
  12. Console.WriteLine(t.Name);//报错
  13. }
  14. }

如果我们可以将类型参数T限定为Person类型,那么在泛型内部就可以操作Person类型的成员了。

三、泛型的约束

表格来至微软官方文档

约束 描述
where T:结构 类型参数必须是值类型。 可以指定除 Nullable 以外的任何值类型。
where T:类 类型参数必须是引用类型;这同样适用于所有类、接口、委托或数组类型。
where T:new() 类型参数必须具有公共无参数构造函数。 与其他约束一起使用时,new() 约束必须最后指定。
where T:<基类名称> 类型参数必须是指定的基类或派生自指定的基类。
where T:<接口名称> 类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。
where T:U 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。
  1. 示例7
  2. public class PrintEmployeeInfo<T> where T:Person
  3. {
  4. public void Print(T t)
  5. {
  6. Console.WriteLine(t.Name);
  7. }
  8. }

四、协变和逆变很简单

有一定工作经验的开发人员一定遇到过下面这样的情况:

  1. 示例8
  2. List<Employee> list = new List<Employee>();
  3. list.Add(new Employee() { Age = 20, Name = "小明" });
  4. IEnumerable<Person> perList;
  5. perList = list;
  6. foreach (var item in perList)
  7. {
  8. Console.WriteLine("名字:" + item.Name + ",年龄:" + item.Age);
  9. }

不是说,不同类型实参构造的泛型也是不同的吗,为啥可以将List

  1. 示例9
  2. public static void PrintEmployee(Person item)
  3. {
  4. Console.WriteLine("名字:" + item.Name + ",年龄:" + item.Age);
  5. }
  6. Action<Employee> empAction = PrintEmployee;
  7. empAction(new Employee() { Age = 20, Name = "小明" });
  8. Action<Person> perAction = PrintEmployee;
  9. perAction(new Employee() { Age = 20, Name = "小明" });

执行结果正常输出



为什么可以将参数类型为Person的方法分别赋值给Action

  1. 类型参数分为输入参数(in)、输出参数(out)和不变参数(没有关键字)
  2. 设计原则:里氏替换原则——派生类(子类)对象能够替换其基类(超类)对象被使用

IEnumerable的定义

public interface IEnumerable<out T> : IEnumerable

示例8中IEnumerable



方法PrintEmployee需要的参数类型是Employee,而Action的输入参数是Person,显然Person不一定是Employee

注:in和out关键字只适用于接口和委托类型

C#基本功之泛型的更多相关文章

  1. C#基本功之委托和事件

    定义:委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用. 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联 目的:方法声明和方法实现的分离,使得程序更容易扩展 一 ...

  2. Java Collections API和泛型

    Java Collections API和泛型 数据结构和算法 学会一门编程语言,你可以写出一些可以工作的代码用计算机来解决一些问题,然而想要优雅而高效的解决问题,就要学习数据结构和算法了.当然对数据 ...

  3. 《疯狂java-突破程序员基本功的16课 》笔记总结

    本人最近读完<疯狂java-突破程序员基本功的16课 >读完后,感觉对java基础又有了新的认识,在这里总结一下:一.数组与内存控制 1.1 数组初始化     java语言的数组是静态的 ...

  4. 扫盲:Kotlin 的泛型

    引子 相信总是有很多同学,总是在抱怨泛型无论怎么学习,都只是停留在一个简单使用的水平,所以一直为此而备受苦恼. Kotlin 作为一门能和 Java 相互调用的语言,自然也支持泛型,不过 Kotlin ...

  5. 一起学 Java(三) 集合框架、数据结构、泛型

    一.Java 集合框架 集合框架是一个用来代表和操纵集合的统一架构.所有的集合框架都包含如下内容: 接口:是代表集合的抽象数据类型.接口允许集合独立操纵其代表的细节.在面向对象的语言,接口通常形成一个 ...

  6. .NET面试题系列[8] - 泛型

    “可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用.“ - Jon Skeet .NET面试题系列目录 .NET面试题系列[1] - .NET框架基础知识(1) .NET面试题系列[2] ...

  7. C#4.0泛型的协变,逆变深入剖析

    C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...

  8. 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)

    建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...

  9. 6.在MVC中使用泛型仓储模式和依赖注入实现增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

随机推荐

  1. Java第十一周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线程 1.互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问) ...

  2. JavaScript的5中基本数据类型

    javascript的5种基本数据类型有: Undefined,Null,Bollean,Number,String,1种复杂数据类型:Object. 1Boolean类型 将一个值转换为Bollea ...

  3. 运用GRASP原则来做uml交互类图-------pos机实例

    重要的几个GRASP原则:1.控制器模式   2.创建者模式 (原则)3.信息专家模式(原则) 4. 高内聚 低耦合   这里所说的模式并不是java中针对具体的事件的设计模式 主成功场景的几个操作: ...

  4. Exception in thread "main" org.hibernate.TransientObjectException: object references an unsaved tran

    今天在使用一对多,多对一保存数据的时候出现了这个错误 Hibernate错误: Exception in thread "main" org.hibernate.Transient ...

  5. 《Head First Java》读书笔记(2) - Java面向对象思想

    1.了解继承 对象继承实际上就是一种"is - a"的关系,如上图的"PantherMan is a SuperHero?",是,那么便属于继承的理解. 继承能 ...

  6. 利用ASCII码生成指定规则的字符串

    /** * 上送终端编号的后两位生成规则 总共可以生成 (36*36-1)1295个编号 * 01...09 0A...0Z * 10...19 1A...1Z * ............... * ...

  7. JavaScript中DOM

    概念 什么是DOM 1. 什么是 DOM DOM 的全称是document object model 它的基本思想是将结构化文佳例如HTML xml解析成一系列的节点.就像一颗树一样. 所有的节点和最 ...

  8. Ubuntu Server 12.04安装图解教程

                                                                                                Ubuntu S ...

  9. struts1.3学习

    一.基本配置 参考博客 项目结构 web.xml <!-- struts配置 --> <servlet> <servlet-name>action</serv ...

  10. oracle pl/sql 控制结构(分支,循环,控制)

    一.pl/sql的进阶--控制结构在任何计算机语言(c,java,pascal)都有各种控制语句(条件语句,循环结构,顺序控制结构...),在pl/sql中也存在这样的控制结构.在本部分学习完成后,希 ...