.NET进阶篇-语言章-1-Generic泛型深入
内容目录
一、概述二、泛型的好处三、泛型使用1、泛型方法2、泛型类、泛型接口四、泛型的功能1、泛型中的默认值2、约束3、协变逆变5、泛型委托4、泛型缓存五、总结
一、概述
泛型我们一定都用过,最常见的List<T>集合。.NET2.0开始支持泛型,创建的目的就是为了不同类型创建相同的方法或类,也包括接口,委托的泛型。比如常见的ORM映射,一个方法通过传入不同的类,返回不同的类实例,再调用时才确定参数类型。
我们知道想要一个类相同名称的方法,如果仅参数类型不同,那么要重载。重载会有很多冗余的代码。在.NET1.0时代也可以不用重载,那就是参数类型直接用Object类型,那么任何类型都能传进去了,但是会有装箱拆箱操作,影响性能。
public static void Show(string sValue)
{
Console.WriteLine(sValue);
}
public static void Show(int iValue)
{
Console.WriteLine(iValue);
}
public static void Show(object oValue)
{
Console.WriteLine(oValue);
}
二、泛型的好处
值类型和引用类型的装箱拆箱消耗。值类型分配在线程栈上,引用类型分配在堆上,只把指针放在栈上。如图所示,如果把int类型1装箱,就要把1拷贝到堆中,就会有内存的交换。以前的ArrayList就是类型不安全的,需要频繁的进行装拆箱操作,Add元素的时候全部装箱object,取的时候要拆箱,性能损失比较大。

泛型的效率等同于硬编码的方式,就是和你很多功能相同的类效率差不多。泛型每个类型只实例化一次,下面泛型缓存会详细解读下。先简单介绍下CLR的运行原理(详细在CLR章节)以了解泛型的原理机制。

.NET编译器和解释器两阶段,我们先经过编译器编译成IL中间语言(dll、exe),和java的字节码类似,然后经过JIT解释成机器码。这样做的好处就是我们只需要编译成IL后,在各个不同计算机系统上,只要有对应的CLR(JIT)就行,这样就和平台无关。二次编译:为了一次编译,不同平台使用。泛型在第一个编译时会用一个占位符代替,在第二次运行时会编译成具体的类型。所以性能相当于硬编码的方式,每种类型最终都有自己的机器码。
List<T>是在使用时定义类型,JIT编译器解析时动态的生成,如定义List<int>,在JIT运行时就声称List<int>类型,然后操作就不会出现装箱拆箱,而且只能添加指定的类型,这就类型安全。
三、泛型使用
1、泛型方法
常见的泛型方法就是在方法后面带上<T>(T param),“T”可以随便定义,只要不是关键保留字就行,默认约定俗成都用T,此处就代表你定义了一个T类,然后后面参数就可以用这个T类型。(如果把鼠标光标放在参数类型T上,然后F12转到定义就会定位到前面这个T。)这样就可以用一个方法,满足不同的参数类型,去做相同的事情。把参数的类型申明推迟到调用时,延迟声明。后面框架中也会有很多这种延迟思想,延迟以达到更好的扩展。
public static void Show<T>(T tValue)
{
Console.WriteLine(tValue);
}
CommonMethod.Show<int>(123);
2、泛型类、泛型接口
创建方法类似,语法一样<T>。用的最多的List<T>就是很典型的泛型类,用来满足不同的具体类型,完成相同的事情。
public class GenericClass<T>
{
public T _T;
}
public interface IGenericInterface<T>
{
T GetT();
}
四、泛型的功能
1、泛型中的默认值
既然用了泛型,那么在内部想要初始化怎么办呢?因为泛型进来的类型不一定是值类型或引用类型,所以初始化就不能简单直接赋null。这个时候需要用到default关键字,用于将泛型类型初始化为null或其他值类型默认值(0,0001/1/1 0:00:00日期等);
2、约束
泛型导致任何类型都可以进来,那么如何去使用这个类型T,编写的时候我们是不知道T是什么,也不知道它能干什么。一个方法就是可以用反射,任何一个类型通过发射都能获取内部的结构属性方法调用。泛型约束提供更简便的方法。在声明泛型时在参数后面追加where关键字。约束可以同时指定多个,像这样where:T People,IWork,new()。同时约束传进来的类型People或其子类,并且继承了IWork接口,有无参数构造函数。

public static void Show<T>(T tValue) where T : People
{
Console.WriteLine(tValue.Name);
}
3、协变逆变
协变逆变就是对参数和返回值的类型进行转换。协变用一个派生更大的类去代替某个类型(小代替大),其实就是设计原则的里氏替换原则,比如狗继承自动物,那么任何用动物作为参数类型的地方,调用时都可以用狗代替。逆变就是反过来。
//协变
public void ShowName(Animal animal)
{
}
ShowName(dog);
泛型接口的协变逆变。如果泛型类型用了out关键字标注,泛型接口就是协变的。这也意味着返回类型只能是T。如果用了in关键字标注,就是逆变,只能把泛型类型T用作方法的输入。这块很绕,实际使用非常少。
//一堆狗肯定是一堆动物啊,为啥就不能这么做呢?下面这句编译不通过
//前后两个类型是没有父子关系的
List<Animal> animalLst = new List<Dog>();
//下面这句就可以呢?
IEnumerable<Animal> animalLst2 = new List<Dog>();
//因为在接口中添加了out关键字
public interface IEnumerable<out T> : IEnumerable
{
//
// 摘要:
// Returns an enumerator that iterates through the collection.
//
// 返回结果:
// An enumerator that can be used to iterate through the collection.
IEnumerator<T> GetEnumerator();
}
ICustomListIn<Dog> customLstIn = new CustomListIn<Animal>();
public interface ICustomListIn<in T>
{
void Show(T t);
}
public class CustomListIn<T> : ICustomListIn<T>
{
public void Show(T t)
{
Console.WriteLine(typeof(T).FullName);
}
}
interface ISetData<in T> //使用逆变
{
void SetData(T data);
}
interface IGetData<out T> //使用协变
{
T GetData();
}
class MyTest<T> : ISetData<T>, IGetData<T>//继承两个泛型接口
{
private T data;
public void SetData(T data)
{
this.data = data; //赋值
}
public T GetData()
{
return this.data; //取数据
}
}
MyTest<object> my = new MyTest<object>();
ISetData<string> set = my;
set.SetData("nihao");
其实协变逆变就是语法糖,为了让不是继承关系的类型也可以互相赋值编译通过。运行时实际右边是什么类型就是什么类型。(欺骗编译器,自己应该会很少写协变逆变的接口或委托)
5、泛型委托
以Action为例,Action是.NET Framework内置的泛型委托,可以使用Action委托以参数形式传递方法,而不用显示声明自定义的委托。其实我们撸代码过程中不太需要自己定义委托,内置的Action和Func就够用,也便于统一。Action无返回值委托,可以有16个参数,可以传入不同的类型。在委托事件一章会详细介绍。

4、泛型缓存
泛型类的静态成员只能在类的一个实例中共享。运行时泛型类的实例已经指定了具体类型,每一个不同的泛型类实例共享静态成员,利用这个特点就可以做缓存。每一个不同的T缓存一个版本数据。如例子所示,当第一次指定不同的T时,会重新构造,再次有相同的类型时,就不会进入静态构造函数了。相当于为缓存了多个版本的静态成员。比如在各个数据库实体类需要有一些增删改查的SQL时,就可以利用用泛型特性,每一个数据库实体类都会缓存一份自己的增删改查SQL。
public class GenericCache<T>
{
static GenericCache()
{
Console.WriteLine("进入静态构造函数");
_TypeTime = $"{typeof(T).FullName}_{DateTime.Now.ToString()}";
}
private static string _TypeTime = "";
public static string GetCache()
{
return _TypeTime;
}
}
Console.WriteLine("************************");
Console.WriteLine(GenericCache<int>.GetCache());
Thread.Sleep(1000);
Console.WriteLine(GenericCache<string>.GetCache());
Thread.Sleep(1000);
Console.WriteLine("认真比较打印出的静态成员值");
Console.WriteLine(GenericCache<int>.GetCache());
Thread.Sleep(1000);
Console.WriteLine(GenericCache<string>.GetCache());
Console.WriteLine("************************");

五、总结
通过泛型类可以创建独立于类型的类,泛型方法创建出独立于类型的方法。接口、结构、委托也可以用泛型的方式创建。建议如果我们需要设计和类型无关的对象时,可以使用泛型,把锅甩给调用方,由上端决定实例化具体什么类型。
如果手机在手边,也可以关注下vx:xishaobb,互动或获取更多消息。当然这里也一直更新de。

.NET进阶篇-语言章-1-Generic泛型深入的更多相关文章
- .NET进阶篇-语言章-2-Delegate委托、Event事件
知识只有经过整理才能形成技能 整个章节分布简介请查看第一篇 内容目录 一.概述 二.解析委托知识点 1.委托本质 2.委托的使用 3.委托意义 逻辑解耦,减少重复代码 代码封装支持扩展 匿名方法和La ...
- 慕课网javascript 进阶篇 第九章 编程练习
把平常撸的码来博客上再撸一遍既可以加深理解,又可以理清思维.还是很纯很纯的小白,各位看官老爷们,不要嫌弃.最近都是晚睡,昨晚也不例外,两点多睡的.故,八点起来的人不是很舒服,脑袋有点晕呼呼,鉴于昨晚看 ...
- IOS开发之进阶篇第一章 - 姿势识别器UIPanGestureRecognizer
今天讲一下姿势识别器,UIGestureRecognizer这个是抽象类 1.拍击UITapGestureRecognizer (任意次数的拍击) 2.向里或向外捏UIPinchGestureReco ...
- 前端开发工程师 - 02.JavaScript程序设计 - 第2章.进阶篇
第2章--进阶篇 类型进阶 类型: Undefined Null Boolean String Number Object 原始类型(值类型):undefined, null, true, " ...
- MYSQL(进阶篇)——一篇文章带你深入掌握MYSQL
MYSQL(进阶篇)--一篇文章带你深入掌握MYSQL 我们在上篇文章中已经学习了MYSQL的基本语法和概念 在这篇文章中我们将讲解底层结构和一些新的语法帮助你更好的运用MYSQL 温馨提醒:该文章大 ...
- 进阶篇,第二章:MC与Forge的Event系统
<基于1.8 Forge的Minecraft mod制作经验分享> 这一章其实才应该是第一章,矿物生成里面用到了Event的一些内容.如果你对之前矿物生成那一章的将算法插入ORE_GEN_ ...
- JavaScript进阶篇 - -第1章 系好安全带
第1章 系好安全带 html,body { font-size: 15px } body { font-family: Helvetica, "Hiragino Sans GB", ...
- 进阶篇:4)面向装配的设计DFA总章
本章目的:理解装配的重要性,明确结构工程师也要对装配进行设计. 1.基础阅读 ①进阶篇:1)DFMA方法的运用: ②需要一台FDM3d打印机:请查看 基础篇:8)结构设计装备必备: 2.为什么要学习D ...
- go语言之进阶篇接口转换
1.go语音之进阶篇 示例: package main import "fmt" type Humaner interface { //子集 sayhi() } type Pers ...
随机推荐
- macOS平台下Qt应用程序菜单翻译及调整
一.翻译 在macOS平台上,系统会为应用程序菜单添加一些额外的菜单项.先来看一些典型的例子: 这个是Qt Creator的菜单,系统为应用程序菜单添加了一些桌面显示操作相关的菜单项: 这个是Qt D ...
- CodeForces - 445B - DZY Loves Chemistry-转化问题
传送门:http://codeforces.com/problemset/problem/445/B 参考:https://blog.csdn.net/littlewhite520/article/d ...
- 2018中国大学生程序设计竞赛 - 网络选拔赛 hdu Find Integer 数论
Find Integer Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Tota ...
- 在Linux查看版本命令
1.在终端中执行下列指令: cat /etc/issue 可以查看当前正在运行的 Ubuntu 的版本号. 2. 使用 lsb_release 命令也可以查看 Ubuntu 的版本号,与方法一相比,内 ...
- 深入vue源码,了解vue的双向数据绑定原理
大家都知道vue是一种MVVM开发模式,数据驱动视图的前端框架,并且内部已经实现了双向数据绑定,那么双向数据绑定是怎么实现的呢? 先手动撸一个最最最简单的双向数据绑定 <div> < ...
- MariaDB数据库自学一
在CentOS下安装Mariadb 数据库,命令: yum -y mariadb mariadb.server 等待几分钟后就可以自动完成安装了,然后启动对应的服务: systemctl start ...
- Accuarcy and Precision
机器学习中,Accuarcy 和 Precision 有什么区别呢? Accuracy = (TP+TN)/TOTAL SAMPLES 也就是计算正确的样本数,占到总样本数的比率 定义是: 对于给定的 ...
- 基于Python的多线程与多进程
1.I/O密集型与计算密集型 多进程适用于I/O密集型 多进程适用于计算密集型 2.没有sleep(T)的多个死循环只能用多进程 3.模块介绍: 1)threading模块(_thread模块已淘汰) ...
- 猿类如何捕获少女心--难以琢磨的try-catch
背景故事 影片<金刚>是2005年上映的一部冒险电影,它讲述1933年的美国,一名勇于冒险的企业家及电影制作者,率领摄制队伍到荒岛拍摄,其中包括女主角安及编剧杰克,他们遇到恐龙及当地土著的 ...
- Centos7上安装jdk8
CentOS7 下安装jdk8环境 1 检查服务器环境 首先,我们需要检查一下服务器是否安装过java环境,可以使用如下命令: java -version 如果已经安装有java环境,会出现类似于以下 ...