泛型Generic:泛型方法、泛型类集合、泛型接口、泛型委托、泛型缓存字典(性能之王)、协变逆变、Object类、C#编译运行原理
参考:
IQueryable、IEnumerable、List的区别和互相转换
泛型是什么:
泛型Generic [dʒəˈnerɪk] 将类型参数的概念引入 .NET Framework,这样就可以设计具有以下特征的类和方法:在客户端代码声明并初始化这些类或方法之前,这些类或方法会延迟指定一个或多个类型。 例如,通过使用泛型类型参数 T
,可以编写其他客户端代码能够使用的单个类,而不会产生运行时转换或装箱操作的成本或风险。
为什么要泛型:
因为泛型可以解决因为参数类型不同而需要重复定义方法、类、接口问题,以达到一个方法当做多个方法使用的功能
泛型怎么用:
泛型方法、泛型类集合、泛型接口、泛型委托、泛型缓存字典
根命名空间:
System 命名空间包含用于定义常用值和引用数据类型、事件和事件处理程序、接口、特性以及处理异常的基础类和基类。
System.Collections.Generic)
- 使用泛型类型可以最大限度地重用代码、保护类型安全性以及提高性能。
- 泛型最常见的用途是创建集合类。
- ArrayList。
- 可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
- 可以对泛型类进行约束以访问特定数据类型的方法。
- 在泛型数据类型中所用类型的信息可在运行时通过使用反射来获取
泛型方法: 泛型方法是通过类型参数声明的方法
这是所有 .NET 类的最终基类;它是类型层次结构的根。
不适用泛型方法:例如要做多个不同类型参数的方法,最后转换位Object类型,经过了装箱拆箱,但是性能低,直接用委托,基本不影响性能。
static void Main(string[] args)
{
void get(object obj) { Console.WriteLine(obj); }
get(123);
get("string");
get(DateTime.Now);
}
下面是使用泛型方法:定义方法时参数类型不确定用<T>,还可以多个参数,调用时可传入各种类型的参数
static void Main(string[] args) { GenericMethod<string>("string"); GenericMethod<int>(123); GenericMethod<DateTime>(DateTime.Now); } //泛型方法:传入类型不确定 public static void GenericMethod<T>(T tParameter) { Console.WriteLine(tParameter); }
运行结果:
泛型类(集合):
无论存储数据的类型如何,添加项和从集合删除项等操作的执行方式基本相同。常用的如下:
- List<T>
- Dictionary<TKey,TValue>
- HashSet<T>等
List<T>用法:
public class Student //实体类 { public int ID { get; set; } public string Name { get; set; } } public List<Student> GetList() //填充数据到集合 { List<Student> studentList = new List<Student>(); //实例化List<Student>集合 Student student = new Student() { }; //实例化Student实体类 student.ID = 1; //填充数据到实体类 student.Name = "张三"; studentList.Add(student); //把实体类数据填充到集合中 return studentList; }
泛型类型参数:
可创建任意数量的构造类型实例,其中每个使用不同的类型参数
类型参数约束:
通过使用 where
上下文关键字指定约束。
public T GetAll<T,Y,U,I,O,P,A,S>() //通过where关键字约束 where T : struct //类型参数必须是不可为 null 的值类型 where Y : class //类型参数必须是引用类型。 此约束还应用于任何类、接口、委托或数组类型。 where U : notnull //类型参数必须是不可为 null 的类型 where I : unmanaged //类型参数必须是不可为 null 的非托管类型 where O : new() //类型参数必须具有公共无参数构造函数 where P : <"基类名"> //类型参数必须是指定的基类或派生自指定的基类。 where A : <"接口名称"> //类型参数必须是指定的接口或实现指定的接口 where S : U // T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数 { return new T(); }
泛型接口:
System.Collections.Generic 命名空间中的集合类。
泛型和数组:
IList<T> 接口无法用于添加元素或从数组删除元素。
泛型委托:
引用泛型委托的代码可以指定类型参数以创建封闭式构造类型,就像实例化泛型类或调用泛型方法一样
泛型和反射:
因为公共语言运行时 (CLR) 能够在运行时访问泛型类型信息,所以可以使用反射获取关于泛型类型的信息,方法与用于非泛型类型的方法相同
泛型和特性:
仅允许自定义属性引用开放式泛型类型(即未向其提供任何类型参数的泛型类型)和封闭式构造泛型类型(即向所有类型参数提供参数的泛型类型)。
泛型缓存字典(性能之王):
是通过静态构造函数只被执行一次的调用机制,在首次执行时存好值,后面不再计算直接调用,以达到缓存下效果。
静态构造函数:将在创建第一个实例或引用任何静态成员之前自动调用静态构造函数。
说人话就是:只有该类第一次被调用时执行一次静态构造函数,该类后面再继续被调用时都不会再去执行静态构造函数。(跟踪调试发现确实这样,编译器能识别)
public class GenericCacheTest { public static void Show() { for (int i = 0; i < 5; i++) { Console.WriteLine(GenericCache<int>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<long>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<DateTime>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<string>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<GenericCacheTest>.GetCache()); Thread.Sleep(10); } } } /// <summary> /// 字典缓存: /// 原理:静态属性常驻内存,是key-value的hash存储,每次调用时需要去内存器查找要进行哈希运算 /// </summary> public class DictionaryCache { private static Dictionary<Type, string> _TypeTimeDictionary = null; static DictionaryCache()//静态构造函数 { Console.WriteLine("This is DictionaryCache 静态构造函数"); _TypeTimeDictionary = new Dictionary<Type, string>(); } public static string GetCache<T>() { Type type = typeof(Type); if (!_TypeTimeDictionary.ContainsKey(type)) { _TypeTimeDictionary[type] = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff")); } return _TypeTimeDictionary[type]; } } /// <summary> /// 泛型字典缓存: /// 原理:泛型缓存则是直接被JIT编译成一个代码段,是存在寄存器(寄存器是CPU内部用来存放数据的一些小型存储区域), /// 这个代码段又有静态缓存,可以直接用,所以性能比字典高 /// /// 每个不同的T,都会生成一份不同的副本,适合不同类型,需要缓存一份数据的场景,泛型缓存字典是缓存字典性能的几十倍乃至上百倍 /// 局限性:只能为某个类型存一个结果,例如为int类型存一个结果,又要为string类型存一个结果 /// </summary> /// <typeparam name="T"></typeparam> public class GenericCache<T> { static GenericCache() { Console.WriteLine("This is GenericCache 静态构造函数"); _TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff")); } private static string _TypeTime = ""; public static string GetCache() { return _TypeTime; } }
协变 Covariance 和 逆变 Contravariance:
协变保留分配兼容性,逆变则与之相反。
说人话:协变是右边类继承左边类,右边参数继承左边参数;泛型修饰符 out
逆变是右边类继承左边类,左边参数继承右边参数;泛型修饰符 in
new List<String>(); //协变:右边List<>继承了左边IEnumerable<>接口,右边String类继左边Object类
new List<Object>(); //逆变:右边List<>继承了左边IEnumerable<>接口,左边边String类继右边Object类
记忆方法:等号左边集合比右边集合小, 协变尖括号内是左边小于右边,逆变是尖括号内左边大于右边 (备注:基类小于当前类)
自定义协变逆变:关键字out、in (一般使用不多,都是调用系统的或者调用别人的协变逆变)
父类变量用子类来实例,例如:鸟类Bird b =new 麻雀Sparrow,反过来就不行
Bird bird2 = new Sparrow(); //可以,Bird是父类, Sparrow是子类
//List<Bird> birdList2 = new List<Sparrow>(); //报错,因为List是类,List<Bird>与List<Sparrow>()没有继承关系
public interface ICustomerListOut<out T>{ T Get(); //定义接口
public class CustomerListOut<T> : ICustomerListOut<T>{ public T Get(){ return default(T); } } //继承接口,且实现方法
ICustomerListOut<Bird> customerList2 = new CustomerListOut<Sparrow>() //与调用系统的一样,要右边继承左边
泛型编译运行原理:
DLL/EXE:包含metadata(元数据)和IL
CLR/JIT:根据metadata来识别加载对应的对象