泛型Generic:泛型方法、泛型类集合、泛型接口、泛型委托、泛型缓存字典(性能之王)、协变逆变、Object类、C#编译运行原理
参考:
IQueryable、IEnumerable、List的区别和互相转换
泛型是什么:
泛型Generic [dʒəˈnerɪk] 将类型参数的概念引入 .NET Framework,这样就可以设计具有以下特征的类和方法:在客户端代码声明并初始化这些类或方法之前,这些类或方法会延迟指定一个或多个类型。 例如,通过使用泛型类型参数 T
,可以编写其他客户端代码能够使用的单个类,而不会产生运行时转换或装箱操作的成本或风险。
为什么要泛型:
因为泛型可以解决因为参数类型不同而需要重复定义方法、类、接口问题,以达到一个方法当做多个方法使用的功能
泛型怎么用:
泛型方法、泛型类集合、泛型接口、泛型委托、泛型缓存字典
根命名空间:
System 命名空间包含用于定义常用值和引用数据类型、事件和事件处理程序、接口、特性以及处理异常的基础类和基类。
泛型概述:(泛型命名空间是重点:System.Collections.Generic)
- 使用泛型类型可以最大限度地重用代码、保护类型安全性以及提高性能。
- 泛型最常见的用途是创建集合类。
- .NET Framework 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类。 应尽可能使用这些类来代替某些类,如 System.Collections 命名空间中的 ArrayList。
- 可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
- 可以对泛型类进行约束以访问特定数据类型的方法。
- 在泛型数据类型中所用类型的信息可在运行时通过使用反射来获取
泛型方法: 泛型方法是通过类型参数声明的方法
Object类:支持 .NET 类层次结构中的所有类,并为派生类提供低级别服务。 这是所有 .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>用法:
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; }
泛型类型参数:
在泛型类型或方法定义中,类型参数是在其创建泛型类型的一个实例时,客户端指定的特定类型的占位符。 泛型类(例如泛型介绍中列出的 GenericList<T>
)无法按原样使用,因为它不是真正的类型;它更像是类型的蓝图。 若要使用 GenericList<T>
,客户端代码必须通过指定尖括号内的类型参数来声明并实例化构造类型。 此特定类的类型参数可以是编译器可识别的任何类型。 可创建任意数量的构造类型实例,其中每个使用不同的类型参数
类型参数约束:
约束告知编译器类型参数必须具备的功能。 在没有任何约束的情况下,类型参数可以是任何类型。 编译器只能假定 System.Object 的成员,它是任何 .NET 类型的最终基类。 有关详细信息,请参阅使用约束的原因。 如果客户端代码尝试使用约束所不允许的类型来实例化类,则会产生编译时错误。 通过使用 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(); }
泛型接口:
为泛型集合类或表示集合中的项的泛型类定义接口通常很有用处。 为避免对值类型的装箱和取消装箱操作,泛型类的首选项使用泛型接口,例如 IComparable<T>而不是 IComparable。 .NET Framework 类库定义多个泛型接口,以将其用于 System.Collections.Generic 命名空间中的集合类。
泛型和数组:
在 C# 2.0 和更高版本中,下限为零的单维数组自动实现 IList<T>。 这可使你创建可使用相同代码循环访问数组和其他集合类型的泛型方法。 此技术的主要用处在于读取集合中的数据。 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
IEnumerable<Object> listObjects = new List<String>(); //协变:右边List<>继承了左边IEnumerable<>接口,右边String类继左边Object类
IEnumerable<String> listObjects = 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来识别加载对应的对象
本文版权归作者和博客园共有,欢迎转载,但必须在文章页面给出原文链接,否则保留追究法律责任的权利。