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 个人理解(不做参考)

协变:子类向父类转换

逆变:父类向子类转换

01.泛型Generic的更多相关文章

  1. Java - 泛型 ( Generic )

    Java - 泛型 ( Generic )     > 泛型的特点         > 解决元素存储的安全性问题         > 解决获取数据元素时,需要类型强转的问题     ...

  2. Java之集合初探(二)Iterator(迭代器),collections,打包/解包(装箱拆箱),泛型(Generic),comparable接口

    Iterator(迭代器) 所有实现了Collection接口的容器都有一个iterator方法, 用来返回一个实现了Iterator接口的对象 Iterator对象称作迭代器, 用来方便的实现对容器 ...

  3. 谈一谈从 Delphi 2009 之后就支援的重要功能 – 泛型 (Generic)

    前言 在C++的语言基础当中,除了物件导向.事件驱动的概念之外,模版设计(Template)也是非常重要的一环.然而,C++的开发人员能够善用模版设计的并不多.模版设计这个好物,一般还有一个名称,就是 ...

  4. JAVA中的泛型(Generic)

    Java泛型(Generic)简介 泛型是jdk1.5版本以后推出来的,表示类型参数化,让java能更具有动态性一些,让类型能变成参数传递. 要我自己感觉的话,泛型本身没啥用,跟反射在一起用,就体现出 ...

  5. Dephi泛型generic的应用

    Dephi泛型generic的应用   泛型在C++, C#中已有广泛应用,Delphi自2009版本也引入泛型,典型的应用如TList,TDictionary.如果你熟悉C#,其用法十分类似. 比如 ...

  6. Java基础之Comparable接口, Collections类,Iterator接口,泛型(Generic)

    一.Comparable接口, Collections类 List的常用算法: sort(List); 排序,如果需要对自定义的类进行排序, 那就必须要让其实现Comparable接口, 实现比较两个 ...

  7. Java自学-集合框架 泛型Generic

    ArrayList上使用泛型 步骤 1 : 泛型 Generic 不指定泛型的容器,可以存放任何类型的元素 指定了泛型的容器,只能存放指定类型的元素以及其子类 package property; pu ...

  8. C#泛型(Generic)

    一.什么是泛型 泛型(Generic)是C#语言2.0.通用语言运行时(CLR)2.0..NET Framework2.0推出来的新特性. 泛型为.NET框架引入类型参数(Type Parameter ...

  9. .NET知识梳理——1.泛型Generic

    1. 泛型Generic 1.1        引入泛型:延迟声明 泛型方法声明时,并未写死类型,在调用的时候再指定类型. 延迟声明:推迟一切可以推迟的. 1.2        如何声明和使用泛型 泛 ...

随机推荐

  1. DarkGreenTrip博客搭建成功

    本博客(https://www.cnblogs.com/zhangshuhao1116)自2021年6月19日由 Shu-How Z  搭建成功,2018年搭建过hexo+next.Wordpress ...

  2. 『无为则无心』Python基础 — 12、Python运算符详细介绍

    目录 1.表达式介绍 2.运算符 (1)运算符的分类 (2)算数运算符 (3)赋值运算符 (4)复合赋值运算符 (5)比较运算符 3.逻辑运算符 拓展1:数字之间的逻辑运算 拓展2:Python中逻辑 ...

  3. Linux中系统时间同步ntpdate简介

    Linux服务器运行久时,系统时间就会存在一定的误差,一般情况下可以使用date命令进行时间设置,但在做数据库集群分片等操作时对多台机器的时间差是有要求的,此时就需要使用ntpdate进行时间同步.所 ...

  4. Zabbix5.0钉钉报警(centos7)

    2.1.到钉钉官网下载pc版钉钉,安装.注册.登陆: 钉钉下载地址:https://www.dingtalk.com/ 2.2.创建群聊和钉钉机器人: 1.创建群聊,把需要收到报警的人员都拉到这个群: ...

  5. 27、路由 route

    优先于网卡中的配置,但是重启或重启网卡失效,最好加入到开机自启动服务中/etc/rc.local文件中: 27.1.添加网络路由: 目的网络.目的网络掩码,网关.网卡: 网络寻址以路由表中的路由优先: ...

  6. SpringBoot | 1.1 SpringBoot简介

    前言 本博客仅为记录与总结SpringBoot的学习笔记,资料来源: 书籍<深入浅出SpringBoot>第三版 B站尚硅谷<雷丰阳2021版SpringBoot2零基础入门> ...

  7. Centos6.7 minimal安装GitLab8.3.4配置LDAP、发邮件以及升级到GitLab8.5.4

    建议使用非root账户安装,先同步系统时间: ntpdate cn.pool.ntp.org 1.创建用户gitlab 注意:centos下,adduser和useradd的命令效果是一样的,但ubu ...

  8. PHP经典算法之背包问题

    问题:假设有一个背包的负重最多可达8公斤,而希望在背包中装入负重范围内可得之总价物品,假设是水果好了,水果的编号.单价与重量如下所示: 1 栗子 4KG $4500 2 苹果 5KG $5700 3 ...

  9. Source not found for GeneratedMethodAccessor127.invoke(Object, Object[]) line: not available

    报错:Source not found for GeneratedMethodAccessor127.invoke(Object, Object[]) line: not available 我在使用 ...

  10. 2012年第三届蓝桥杯C/C++程序设计本科B组省赛 密码发生器

    密码发生器 题目描述: ```bash 在对银行账户等重要权限设置密码的时候,我们常常遇到这样的烦恼:如果为了好记用生日吧,容易被破解,不安全:如果设置不好记的密码,又担心自己也会忘记:如果写在纸上, ...