- A+
1. 基本了解
1.1 什么是泛型?
字面意思:不确定的类型
泛型常用:泛型方法,泛型类,泛型接口,泛型委托
1.2 泛型 T(熟悉)
T
的作用,其实就是一个通用的容器,制造它的人开始不指定它是用来装什么的,而使用者在使用它的时候要告诉这个容器准备用来装什么,容器知道了用来装什么之后,后面所有存入操作,它都要检查一下你放的东西是不是开始指定的东西类型
所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型
泛型允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候,换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法
泛型编程是一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更为灵活的复用。在定义泛型类时,在对客户端代码能够在实例化类时,可以用类型参数的类型种类施加限制
原理:泛型的使用是来源于c#2.0新出的规则和框架的升级,对原生需求的变更,泛型不是语法糖,是应对数据类型在传递参数的时候解决多代码冗余问题,减少代码的重复和可维护性。泛型的协变与逆变和泛型约束在c#4.0出现,解决c#出现的代码父类继承问题
1.3 设计思想
泛型的思想表现了一个很重要的架构思想: 延迟思想,推迟一切可以推迟的,使程序有更多的灵活性和扩展性,用来解决,方法中是相同的操作,但是传入参数是不同类型的问题(例举)
1.4 应用场景
类型不明确时:自定义对象的时候,如果我们会定义很多类似的对象,之后参数类型不同,那么我们此时可以考虑在定义对象的时候使用泛型
定义变量,定义方法的参数,定义方法的返回值
示例:返回结果
public class Result<T> { public int code { get; set; } public List<T> date { get; set; } } Result<A> result_a = new Result<A>() { code = 200, date = new List<A>() }; Result<B> result_b = new Result<B>() { code = 200, date = new List<B>() };
1.5 装箱拆箱(了解)
在没有泛型之前,用 object
类型也可以实现相同操作,但是会有些性能损耗及类型安全问题
说明
简单来说,装箱是将值类型转换为引用类型 ;拆箱是将引用类型转换为值类型
装箱:用于在垃圾回收堆中存储值类型。装箱是值类型到 object
类型或到此值类型所实现的任何接口类型的隐式转换
拆箱:从 object
类型到值类型或从接口类型到实现该接口的值类型的显式转换
c#类型
C#中值类型和引用类型的最终基类都是Object
类型(它本身是一个引用类型)。也就是说,值类型也可以当做引用类型来处理。而这种机制的底层处理就是通过装箱和拆箱的方式来进行,利用装箱和拆箱功能,可通过允许值类型的任何值与Object
类型的值相互转换,将值类型与引用类型链接起来
发生场景
一种最普通的场景是,调用一个含类型为Object
的参数的方法,该Object
可支持任意为型,以便通用。当你需要将一个值类型(如Int32
)传入时,需要装箱。
另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object
。于是,要将值类型数据加入容器时,需要装箱
装箱和拆箱的内部操作
.NET中数据类型划分为值类型和引用类型,与此对应,内存分配被分成了两种方式,一为栈,二为堆(托管堆)
值类型只会在栈中分配,引用类型分配内存与托管堆(托管堆对应于垃圾回收)
2. 泛型方法
泛型方法可以定义特定于其执行范围的泛型参数
2.1 定义泛型方法
public class MyClass { // 指定MyMethod方法用以执行类型为X的参数 public void MyMethod<X>(X x) { // } //此方法也可不指定方法参数 public void MyMethod<X>() { // } }
2.2 调用泛型方法
MyClass mycls = new MyClass(); mycls.MyMethod<int>(3);
3. 泛型类
类级别泛型参数的所有约束都必须在类作用范围中定义
3.1 定义泛型类
简单定义
public class MyC<T> { ... } public class MyC<T> { public T Get(){...} public void Show(T t){...} }
普通类继承泛型类
public class MyClass<T>:MyC<T> { ... }
泛型类继承泛型类,继承的泛型类型必须是可推断的(与子类一致或者一个具体的类型)
public class MyClass2<T>:MyC<int> { ... }
3.2 其它示例
示例一
public class MyC<T> where T:new() { public void Show<T>(){T entity} } public class MyClass<T> where T:IComparable<T> { public void MyMethod<X>(X x,T t) { // } }
4. 泛型接口
4.1 定义泛型接口
简单定义
public interface MyInterface<T> { ... } public interface MyInterface<T> { T Get(); void Show(T t); }
泛型接口继承接口
public interface MyInterface2<T>:MyInterface<T> { ... }
5. 泛型委托
在某个类中定义的委托可以使用该类的泛型参数
5.1 定义泛型委托
示例一
public class MyClass<T> { public delegate void GenericDelegate(T t); public void SomeMethod(T t) { } }
5.2 使用泛型委托
示例一:同定义示例一
public GenericMethodDemo() { MyClass<int> obj = new MyClass<int>(); MyClass<int>.GenericDelegate del; del = new MyClass<int>.GenericDelegate(obj.SomeMethod); del(3); }
6. 泛型约束
6.1 类型安全问题
show
方法若使用 object
类型参数,虽然编译器中没有报错,但是在运行中会出现类型转换失败问题,原因是类型 C
并没有继承 A
父类,此处就引发了类型错误问题,而泛型约束就是解决类型安全问题
namespace t1 { class Program { static void Main(string[] args) { A obja = new A() { id = 1, name = "a" }; B objb = new B() { id = 2, name = "b" }; C objc = new C() { id = 3, name = "C" }; try { Show(obja); Show(objb); Show(objc); } catch (Exception ex) { } } static void Show(object oval) { A obj = (A)oval; Console.WriteLine(obj.id); } } public class A { public int id { get; set; } public string name { get; set; } } public class B : A { } public class C { public int id { get; set; } public string name { get; set; } } }
6.2 常用约束列表
约束 | 说明 |
---|---|
where T:基类名 | 类型参数必须是指定的基类或派生自指定的基类 |
where T:接口名称 | 类型参数必须是指定的接口或实现指定的接口 |
where T:class | 类型参数必须是引用类型,包括任何类、接口、委托或数组类型 |
where T:struct | 类型参数必须是值类型,可以指定除 Nullable 以外的任何值类型 |
where T:new() | 类型参数必须具有无参数的公共构造函数,与其他约束同使用时,必须最后指定 |
6.3 常用示例
示例一:接口约束|派生约束
// 1.常见 public class MyGenericClass<T> where T:IComparable { } // 2.约束放在类的实际派生之后 public class B { } public class MyClass6<T> : B where T : IComparable { } // 3.可以继承一个基类和多个接口,且基类在接口前面 public class B { } public class MyClass7<T> where T : B, IComparable, ICloneable { }
示例二:引用类型,值类型约束
public class c<T> where T:class
public class MyClassy<T, U> where T : class where U : struct { }
构造函数约束
以使用 new
运算符创建类型参数的实例;但类型参数为此必须受构造函数约束 new()
的约束。new()
约束可以让编译器知道:提供的任何类型参数都必须具有可访问的无参数(或默认)构造函数。new()
约束出现在 where 子句的最后
// 1.常见的 public class MyClass8<T> where T : new() { } // 2.可以将构造函数约束和派生约束组合起来,前提是构造函数约束出现在约束列表的最后 public class MyClass8<T> where T : IComparable, new() { }
7. 协变逆变(扩展)
协变和逆变只有在泛型接口,泛型委托中才有,协变逆变也可以组合使用
7.1 使用问题
// 鸟类 public class Bird { public int id { get; set; } } // 麻雀类 public class Sparrow:Bird { public string name { get; set; } } class Program { static void Main(string[] args) { // 麻雀也属于鸟类 Bird bird1 = new Bird(); Bird bird2 = new Sparrow(); // 从人类语言上来说,一组麻雀也是一组鸟类 // 但是在程序中,List<Bird> 是一个新的类,与 List<Sparrow> 无父子关系 // List<Bird> list = new List<Sparrow>(); 报错 } }
7.2 协变
协变:使用 out
修饰类型参数,且类型参数只能用作返回值,不可用于输入参数,使得子类可在右边
namespace t2 { // 鸟类 public class Bird { public int id { get; set; } } // 麻雀类 public class Sparrow : Bird { public string name { get; set; } } // 自定义协变 public interface ICustomerListOut<out T> { T Get(); //Show(T t); } public class CustomerListOut<T> : ICustomerListOut<T> { public T Get() { return default(T); } //public void Show(T t) { } } class Program { static void Main(string[] args) { // 协变 IEnumerable<Bird> birds1 = new List<Sparrow>(); // 自定义 ICustomerListOut<Bird> birds2 = new CustomerListOut<Sparrow>(); } } }
7.3 逆变
逆变:使用 in
修饰类型参数,且类型参数只能用作输入参数,不可用于输入参数返回值,使得父类可在右边
namespace t2 { // 鸟类 public class Bird { public int id { get; set; } } // 麻雀类 public class Sparrow : Bird { public string name { get; set; } } public interface ICustomerListIn<in T> { // T Get(); void Show(T t); } public class CustomerListIn<T> : ICustomerListIn<T> { //public T Get() //{ // return default(T); //} public void Show(T t) { ... } } class Program { static void Main(string[] args) { // 逆变 ICustomerListIn<Sparrow> sparrow1 = new CustomerListIn<Bird>(); } } }
7.4 个人理解(不做参考)
协变:子类向父类转换
逆变:父类向子类转换