内容目录

一、概述二、泛型的好处三、泛型使用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泛型深入的更多相关文章

  1. .NET进阶篇-语言章-2-Delegate委托、Event事件

    知识只有经过整理才能形成技能 整个章节分布简介请查看第一篇 内容目录 一.概述 二.解析委托知识点 1.委托本质 2.委托的使用 3.委托意义 逻辑解耦,减少重复代码 代码封装支持扩展 匿名方法和La ...

  2. 慕课网javascript 进阶篇 第九章 编程练习

    把平常撸的码来博客上再撸一遍既可以加深理解,又可以理清思维.还是很纯很纯的小白,各位看官老爷们,不要嫌弃.最近都是晚睡,昨晚也不例外,两点多睡的.故,八点起来的人不是很舒服,脑袋有点晕呼呼,鉴于昨晚看 ...

  3. IOS开发之进阶篇第一章 - 姿势识别器UIPanGestureRecognizer

    今天讲一下姿势识别器,UIGestureRecognizer这个是抽象类 1.拍击UITapGestureRecognizer (任意次数的拍击) 2.向里或向外捏UIPinchGestureReco ...

  4. 前端开发工程师 - 02.JavaScript程序设计 - 第2章.进阶篇

    第2章--进阶篇 类型进阶 类型: Undefined Null Boolean String Number Object 原始类型(值类型):undefined, null, true, " ...

  5. MYSQL(进阶篇)——一篇文章带你深入掌握MYSQL

    MYSQL(进阶篇)--一篇文章带你深入掌握MYSQL 我们在上篇文章中已经学习了MYSQL的基本语法和概念 在这篇文章中我们将讲解底层结构和一些新的语法帮助你更好的运用MYSQL 温馨提醒:该文章大 ...

  6. 进阶篇,第二章:MC与Forge的Event系统

    <基于1.8 Forge的Minecraft mod制作经验分享> 这一章其实才应该是第一章,矿物生成里面用到了Event的一些内容.如果你对之前矿物生成那一章的将算法插入ORE_GEN_ ...

  7. JavaScript进阶篇 - -第1章 系好安全带

    第1章 系好安全带 html,body { font-size: 15px } body { font-family: Helvetica, "Hiragino Sans GB", ...

  8. 进阶篇:4)面向装配的设计DFA总章

    本章目的:理解装配的重要性,明确结构工程师也要对装配进行设计. 1.基础阅读 ①进阶篇:1)DFMA方法的运用: ②需要一台FDM3d打印机:请查看 基础篇:8)结构设计装备必备: 2.为什么要学习D ...

  9. go语言之进阶篇接口转换

    1.go语音之进阶篇 示例: package main import "fmt" type Humaner interface { //子集 sayhi() } type Pers ...

随机推荐

  1. NLP(二) 获取数据源和规范化

    Why we do this 将获取的数据统一格式,得到规范化和结构化得数据 字符串操作 # 创建字符串列表和字符串对象 namesList = ['Tuffy','Ali','Nysha','Tim ...

  2. Windows平台python验证码识别

    参考: http://oatest.dragonbravo.com/Authenticate/SignIn?returnUrl=%2f http://drops.wooyun.org/tips/631 ...

  3. gym/102021/J GCPC18 模拟拼图

    模拟拼图 题意: 给定n块拼图,每个拼图为四方形,对应四条边有四个数字,如果为0,表示这个边是在边界的,其他数字表示和另一个拼图的一条边相接.保证每个非零数只出现两次. 思路: 模拟,但是要注意几个情 ...

  4. codeforces 816 E. Karen and Supermarket(树形dp)

    题目链接:http://codeforces.com/contest/816/problem/E 题意:有n件商品,每件有价格ci,优惠券di,对于i>=2,使用di的条件为:xi的优惠券需要被 ...

  5. codeforces Round #389(Div.2)C Santa Claus and Robot(思维题)

    题目链接:http://codeforces.com/contest/752/problem/C 题意:给出一系列机器人的行动方向(机器人会走任意一条最短路径),问最少标记几个点能让机器人按这个 路径 ...

  6. 中南林业科技大学第十一届程序设计大赛 有趣的二进制 牛客网 java大数的一些方法

    链接:https://www.nowcoder.com/acm/contest/124/C来源:牛客网 题目描述 小新在学C语言的时候,邝老师告诉他double类型的数据在表示小数的时候,小数点后的有 ...

  7. 在CMD命令行进入和退出Python程序

    进入: 直接输入python即可 退出: 方法一:输入exit(),回车 方法二:输入quit(),回车 方法三:CTRL + Z,回车

  8. 创建最简单的exe形式COM组件并在MFC程序调用

    来新公司学习接手新项目,拿到代码打开解决方案看到里面竟然有40几个工程,有点吃惊.具体看代码也有很多之前没见过的写法,上了几天火. 有件事就没太搞明白,按照文档的说法上层很多软件都要调用IO服务器,但 ...

  9. C#开发BIMFACE系列24 服务端API之获取模型数据9:获取单个房间信息

    系列目录     [已更新最新开发文章,点击查看详细] 大厦建筑模型中,基本上包含多个楼层,每个楼层包含多个房间等信息.在<C#开发BIMFACE系列21 服务端API之获取模型数据6:获取单模 ...

  10. hbase读取快照数据-lzo压缩遇到的问题

    1.读取hbase快照数据时报UnsatisfiedLinkError: no gplcompression in java.library.path错: 2019-09-04 17:36:07,44 ...