泛型类型约束

简言之:对泛型类型进行约束,细化,限定。

MSDN的定义:泛型定义中的 where 子句指定对用作泛型类型、方法、委托或本地函数中类型参数的参数类型的约束,意思就是可以有泛型类、泛型方法、泛型委托或泛型接口四类【即where可以写在这4种后面】。

约束可指定接口、基类或要求泛型类型为引用、值或非托管类型。 它们声明类型参数必须具备的功能。意思就是where T,这个T类型参数可以是接口、基类、是引用类型、值类型或非托管类型。

下表列出了五种类型的约束:

约束 说明
T:struct 类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。
T:class 类型参数必须是引用类型,包括任何类、接口、委托或数组类型。
T:new () 类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。
T:<基类名> 类型参数必须是指定的基类或派生自指定的基类。
T:<接口名称> 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。
T:U 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束.

在上一篇文章中我们提到对于以Object作为形参来解决方法复用的时候会有类型安全问题。泛型约束可以解决这一问题。具体如下例所示:

    class Animal
{
public string Name { get; set; } public void Say()
{
Console.WriteLine($"{Name} is saying");
}
}
class Cat : Animal
{ }
class Dog : Animal
{ }

我们想让猫和狗发出叫声,当非动物叫的时候,程序抛异常了。客户端代码如下:

public static void Main(string[] args)
{
Animal dog = new Cat()
{
Name = "dogXiaoHe"
};
Animal Cat = new Cat()
{
Name = "CatXiaoMiao"
};
string NonAnimalDemo = "123";
ShowInfo(dog);
ShowInfo(Cat);
ShowInfo(NonAnimalDemo);
}
    public static void ShowInfo(object obj)
{
Animal animal = (Animal)obj;//必须进行类型判断转换,因为C#是强类型语言,在运行时必须明确其类型。
//dynamic animal = obj;
//if(obj is Animal)
//{
// (obj as Animal).Say();
//}
Console.WriteLine($"name={obj.GetType().Name},type={obj.GetType()},property Name={animal.Name}");
animal.Say();
}

当执行到ShowInfo(NonAnimalDemo)时,程序抛异常,只能在运行期间才能捕捉到异常。

注意一点:此处提供了几种进行类型转换的方法:

1.直接强转,强转失败会抛异常。

2.is as 类型转换,不会抛异常。

3.dynamic关键字.dynamic是FrameWork4.0的新特性。dynamic的出现让C#具有了弱语言类型的特性。编译器在编译的时候不再对类型进行检查(在代码后面加上animal.aaa();方法编译时也不会报错,但是不安全),编译期默认dynamic对象支持你想要的任何特性。

换用泛型方法并进行泛型约束后:编译阶段就能检查错误。

    public static void ShowInfo<T>(T tParam) where T : Animal
{
Console.WriteLine($"name={tParam.GetType().Name},type={tParam.GetType()},property Name={tParam.Name}");
tParam.Say();
}

泛型类型

泛型接口

如上面的例子,当只想让实现IRunning的动物Say时,可以使用泛型接口(或者这么理解,我让这个动物增加了一个running功能,拓展了功能),如下。

class Tiger : Animal, IRunning
{
public void Speed()
{
Console.WriteLine($"{Name} speed is to fast,is runnning");
}
}

泛型接口方法如下:

public static void ShowInfo<T>(T tSParam) where T : Animal, IRunning
{
Console.WriteLine($"name={tSParam.GetType().Name},type={tSParam.GetType()},property Name={tSParam.Name}");
tSParam.Say();
tSParam.Speed();
}

客户端执行,结果如下:

public static void Main(string[] args)
{
Animal dog = new Cat()
{
Name = "dogXiaoHe"
};
Animal Cat = new Cat()
{
Name = "CatXiaoMiao"
};
string NonAnimalDemo = "123";
Tiger tiger = new Tiger()//注意此处的实例化必须是对Tiger
{
Name = "tigerXiaoHu"
};
ShowInfo<Tiger>(tiger);
//ShowInfo<Animal>(dog);会编译失败
Console.ReadKey();
}

泛型类和泛型方法

此处关于泛型方法有一点关键字default需要说明一下:

        /// <summary>
/// 在不知道类型参数为值类型还是引用类型的情况下,为对象实例赋初值,T可能为引用类型;或者是值类型(数值或者结构体)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tParam"></param>
/// <returns>
/// 对于引用类型会返回 null,对于数值类型会返回零。
/// 对于结构,此关键字将返回初始化为零或 null 的每个结构成员,具体取决于这些结构是值类型还是引用类型
/// </returns>
public static T ShowInfoWithReturn<T>(T tParam)
{
return default(T);
}

泛型事件

泛型委托

反射获取泛型类型

通过反射我们可以获取程序集的相关信息,比如该程序集下有多少个类,每个类的属性、字段、方法等信息。那当遇到泛型如何进行相关操作呢。

下面以反射执行某个类的方法为例讲解:

泛型类

        /// <summary>
/// 泛型类
/// 执行GeneircInfo<T>类下的Show<T>方法
/// </summary>
static void ExecGenericClassMethod()
{
Type type = typeof(GeneircInfo<int>);
//Type type = typeof(GeneircInfo<>);
//Type[] actualType = { typeof(int) };
//type = type.MakeGenericType(actualType);
object instance = Activator.CreateInstance(type);
//泛型类下的泛型方法
type.InvokeMember("Show", BindingFlags.InvokeMethod, null, instance, new object[] { 123 });
}
/// <summary>
///泛型类
/// GeneircClass<S,T>类下的ShowGeneircClassInfo<S,T>方法
/// </summary>
static void ExecuteGenricClass()
{
Type type = typeof(GeneircClass<,>);
Type[] actualType = { typeof(string), typeof(int) };
type = type.MakeGenericType(actualType);
object instance = Activator.CreateInstance(type);
type.InvokeMember("ShowGeneircClassInfo", BindingFlags.InvokeMethod, null, instance, new object[] { "abc", 456 });
}

泛型方法

    /// <summary>
/// 泛型方法
/// </summary>
static void ExecuteGenericMethod()
{
Type type = typeof(GeneircMethod);
object instance = Activator.CreateInstance(type);
//常类下获取泛型方法
MethodInfo method = type.GetMethod("Show", BindingFlags.Instance | BindingFlags.Public);
//不加后面这句会报错:不能对 ContainsGenericParameters 为 True 的类型或方法执行后期绑定操作。”
method = method.MakeGenericMethod(typeof(string));
method.Invoke(instance, new object[] { "aaa" });
}

总结:

加入泛型约束后,可以获取更多的功能,可以保证程序的安全性,避免错误调用。

下次讲解协变、逆变。

C#高级编程之泛型二(泛型类型约束、类型、反射与泛型)的更多相关文章

  1. 从头认识java-13.11 对照数组与泛型容器,观察类型擦除给泛型容器带来什么问题?

    这一章节我们继续类型擦除的话题,我们将通过对照数组与泛型容器,观察类型擦除给泛型容器带来什么问题? 1.数组 package com.ray.ch13; public class Test { pub ...

  2. C# 8.0和.NET Core 3.0高级编程 分享笔记二:编程基础第一部分

    基础部分被我分为了2篇,因为实在太多了,但是每一个知识点我都不舍得删除,所以越写越多,这一篇博客整理了4个夜晚,内容有点多建议慢慢看.本章涵盖以下主题: 介绍C# 理解C#的基础知识 使用变量 处理空 ...

  3. C#高级编程笔记 (1至6章节)数组,类/方法/泛型

    2.3变量 var 类型推断 type 类的分类 如:type nametype = name.GetType(); //取变量name的类型 const 常量  const int painame ...

  4. 【读书笔记】C#高级编程 第十二章 动态语言扩展

    (一)DLR C#4的动态功能是Dynamic Language Runtime(动态语言运行时,DLR)的一部分.DLR是添加到CLR的一系列服务. (二)dynamic类型 dynamic类型允许 ...

  5. Unix环境高级编程—进程控制(二)

    一.函数wait和waitpid 今天我们继续通过昨天那个死爹死儿子的故事来讲(便于记忆),现在看看wait和waitpid函数. #include<sys/wait.h> pid_t w ...

  6. 【读书笔记】C#高级编程 第二十二章 安全性

    (一)身份验证和授权 安全性的两个基本支柱是身份验证和授权.身份验证是标识用户的过程,授权在验证了所标识用户是否可以访问特性资源之后进行的. 1.标识和Principal 使用标识可以验证运行应用程序 ...

  7. C# 8.0和.NET Core 3.0高级编程 分享笔记二:编程基础第二部分

    这一篇是接上一篇笔记的第二部分. 2.5深入研究控制台应用程序 前面创建并使用了基本的控制台应用程序,下面更深入地研究它们. 控制台应用程序是基于文本的,在命令上运行的.它们通常执行需要编写脚本的简单 ...

  8. 【读书笔记】C#高级编程 第七章 运算符和类型强制转换

    (一)运算符 类别 运算符 算术运算符 + - * / % 逻辑运算符 & | ^ ~ && || ! 字符串连接运算符 + 增量和减量运算符 ++ -- 移位运算符 < ...

  9. 【读书笔记】C#高级编程 第三章 对象和类型

    (一)类和结构 类和结构实际上都是创建对象的模板,每个对象都包含数据,并提供了处理和访问数据的方法. 类和结构的区别:内存中的存储方式.访问方式(类是存储在堆上的引用类型,结构是存储在栈的值类型)和它 ...

  10. C#高级编程之泛型一(泛型的引入、泛型的使用、何为泛型)

    为何引入泛型 当我们要对不同类型的参数执行类似的方法时:如下所示功能打印传入参数的相关信息. class CommonMethdod { /// <summary> /// show in ...

随机推荐

  1. 第三十二章 Linux常规练习题(一)

    一.练习题一 1.超级用户(管理员用户)提示符是____,普通用户提示符是____.2.linux关机重启的命令有哪些 ?3.bash是什么?4.bash特性, 常见的bash特性有哪些?5.网卡的配 ...

  2. C# 面试前的准备_基础知识点的回顾_02

    1.数据库的范式 这算入门问题了吧,但凡是个数据库类的,都得问吧, 但我们在回答的时候开始背书啦 第一范式(1NF)无重复的列 第二范式(2NF)属性完全依赖于主键 [ 消除部分子函数依赖 ] 第三范 ...

  3. Disconnected from the target VM, address: '127.0.0.1:1135', transport: 'socket'-SpringBoot启动报错

    一.问题由来 本地代码在一次打包后,再次启动项目时报了一个错误,详细的错误信息如下: 2020-10-23 15:10:26.724 [] [main] INFO o.s.c.a.Annotation ...

  4. docker-compose启动consul集群

    version: '2.0' services: consul-server1: image: consul:latest hostname: "consul-server1" p ...

  5. git 常用命令大全2

    查看.添加.提交.删除.找回,重置修改文件 git help <command> # 显示command的help git show # 显示某次提交的内容 git show $id gi ...

  6. 活动可视化搭建系统——你的KPI被我承包了

    前言 对于C端业务偏多的公司来说,在增长.运营等各方同学的摧残下永远绕不过去的一个坑就是大量的H5页面开发,它可能是一个下载.需求告知.产品介绍.营销活动等页面.此类需求都有几个明显的缺点: •开发性 ...

  7. ## 【分布式事务】面试官问我:MySQL中的XA事务崩溃了如何恢复??

    写在前面 前段时间搭建了一套MySQL分布式数据库集群,数据库节点有12个,用来测试各种分布式事务方案的性能和优缺点.测试MySQL XA事务时,正当测试脚本向数据库中批量插入数据时,强制服务器断电! ...

  8. CF715E—— Complete the Permutations

    传送门:QAQQAQ 题意:给你两个$1$~$n$的排列,0表示该位置数字不确定,两两交换第一个排列中的元素使之变成第二个排列,令$s[x]$表示对于所有不同的两个排列,最少交换次数为$x$的序列有$ ...

  9. 通过阿里镜像网站制作iso文件安装CentOS6.9

    基于网络安装 创建kickstart文件的方式: 1.复制模板/root/anaconda-ks.cfg,而后使用vim编辑配置 2.使用system-config-kickstart来生成,建议使用 ...

  10. Loading descriptor for XXX.'has encountered a problem' A internal error occured during:"Loading ....."

    在JavaWeb部署Tomcat后启动Jsp发现这样的报错 这可能是Tomcat的运行欢迎有问题,按下图所示打开Tomcat界面.Servers目录就是当前工作空间对所有工程适用的的Tomcat环境, ...