[C# 基础知识系列]专题八: 深入理解泛型(二)
引言:
本专题主要是承接上一个专题要继续介绍泛型的其他内容,这里就不多说了,就直接进入本专题的内容的。
一、类型推断
在我们写泛型代码的时候经常有大量的"<"和">"符号,这样有时候代码一多,也难免会让开发者在阅读代码过程中会觉得有点晕的,此时我们觉得晕的时候肯定就会这样想:是不是能够省掉一些"<" 和">"符号的呢?你有这种需求了, 当然微软这位好人肯定也会帮你解决问题的,这样就有了我们这部分的内容——类型推断,意味着编译器会在调用一个泛型方法时自动判断要使用的类型,(这里要注意的是:类型推断只使用于泛型方法,不适用于泛型类型),下面是演示代码:
using System; namespace 类型推断例子
{
class Program
{
static void Main(string[] args)
{
int n1 = 1;
int n2 = 2;
// 没有类型推断时需要写的代码
// GenericMethodTest<int>(ref n1, ref n2); // 有了类型推断后需要写的代码
// 此时编译器可以根据传递的实参 1和2来判断应该使用Int类型实参来调用泛型方法
// 可以看出有了类型推断之后少了<>,这样代码多的时候可以增强可读性
GenericMethodTest(ref n1, ref n2);
Console.WriteLine("n1的值现在为:" + n1);
Console.WriteLine("n2的值现在为:" + n2);
Console.Read(); //string t1 = "123";
//object t2 = "456";
//// 此时编译出错,不能推断类型
//// 使用类型推断时,C#使用变量的数据类型,而不是使用变量引用对象的数据类型
//// 所以下面的代码会出错,因为C#编译器发现t1是string,而t2是一个object类型
//// 即使 t2引用的是一个string,此时由于t1和t2是不同数据类型,编译器所以无法推断出类型,所以报错。
//GenericMethodTest(ref t1, ref t2);
} // 类型推断的Demo
private static void GenericMethodTest<T>(ref T t1,ref T t2)
{
T temp = t1;
t1 = t2;
t2 = temp;
}
}
}
代码中都有详细的注释,这里就不解释了。
二、类型约束
如果大家看了我的上一个专题的话,就应该会注意到我在实现泛型类的时候用到了where T : IComparable,在上一个专题并没有和大家介绍这个是泛型的什么用法,这个用法就是这个部分要讲的类型约束,其实where T : IComparable这句代码也很好理解的,猜猜也明白的(如果是我不知道的话,应该是猜类型参数T要满足IComparable这个接口条件,因为Where就代表符合什么条件的意思,然而真真意思也确实如此的)下面就让我们具体看看泛型中的类型参数有哪几种约束的。 首先,编译泛型代码时,C#编译器肯定会对代码进行分析,如果我们像下面定义一个泛型类型方法时,编译器就会报错:
// 比较两个数的大小,返回大的那个
private static T max<T>(T obj1, T obj2)
{
if (obj1.CompareTo(obj2) > 0)
{
return obj1;
} return obj2;
}
如果像上面一样定义泛型方法时,C#编译器会提示错误信息 :“T”不包含“CompareTo”的定义,并且找不到可接受类型为“T”的第一个参数的扩展方法“CompareTo”。 这是因为此时类型参数T可以为任意类型,然而许多类型都没有提供CompareTo方法,所以C#编译器不能编译上面的代码,这时候我们(编译器也是这么想的)肯定会想——如果C#编译器知道类型参数T有CompareTo方法的话,这样上面的代码就可以被C#编译器验证的时候通过,就不会出现编译错误的(C#编译器感觉很人性化的,都会按照人的思考方式去解决问题的,那是因为编译器也是人开发出来的,当然会人性化的,因为开发人员当时就是这么想的,所以就把逻辑写到编译器的实现中去了),这样就让我们想对类型参数作出一定约束,缩小类型参数所代表的类型数量——这就是我们类型约束的目的,从而也很自然的有了类型参数约束(这里通过对遇到的分析然后去想办法的解决的方式来引出类型约束的概念,主要是让大家可以明白C#中的语言特性提出来都是有原因,并不是说微软想提出来就提出来的,主要还是因为用户会有这样的需求,这样的方式我觉得可以让大家更加的明白C#语言特性的发展历程,从而更加深入理解C#,从我前面的专题也看的出来我这样介绍问题的方式的,不过这样也是我个人的理解,希望这样引入问题的方式对大家会有帮助,让大家更好的理解C#语言特性,如果大家对于对于有任何意见和建议的话,都可以在留言中提出的,如果觉得好的话,也麻烦表示认可下)。所以上面的代码可以指定一个类型约束,让C#编译器知道这个类型参数一定会有CompareTo方法的,这样编译器就不会报错了,我们可以将上面代码改为(代码中T:IComparable<T>为类型参数T指定的类型实参都必须实现泛型IComparable接口):
// 比较两个数的大小,返回大的那个
private static T max<T>(T obj1, T obj2) where T:IComparable<T>
{
if (obj1.CompareTo(obj2) > 0)
{
return obj1;
} return obj2;
}
类型约束就是用where 关键字来限制能指定类型实参的类型数量,如上面的where T:IComparable<T>语句。C# 中有4种约束可以使用,然而这4种约束的语法都差不多。(约束要放在泛型方法或泛型类型声明的末尾,并且要使用Where关键字)
(1) 引用类型约束
表示形式为 T:class, 确保传递的类型实参必须是引用类型(注意约束的类型参数和类型本身没有关系,意思就是说定义一个泛型结构体时,泛型类型一样可以约束为引用类型,此时结构体类型本身是值类型,而类型参数约束为引用类型),可以为任何的类、接口、委托或数组等;但是注意不能指定下面特殊的引用类型:System.Object,System.Array,System.Delegate,System.MulticastDelegate,System.ValueType,System.Enum和System.Void.
如下面定义的泛型类:
using System.IO;
public class samplereference<T> where T : Stream
{
public void Test(T stream)
{
stream.Close();
}
}
上面代码中类型参数T设置了引用类型约束,Where T:stream的意思就是告诉编译器,传入的类型实参必须是System.IO.Stream或者从Stream中派生的一个类型,如果一个类型参数没有指定约束,则默认T为System.Object类型(相当于一个默认约束一样,就想每个类如果没有指定构造函数就会有默认的无参数构造函数,如果指定了带参数的构造函数,编译器就不会生成一个默认的构造函数)。然而,如果我们在代码中显示指定System.Object约束时,此时会编译器会报错:约束不能是特殊类“object”(这里大家可以自己试试看的)
(2)值类型约束
表示形式为T:struct,确保传递的类型实参时值类型,其中包括枚举,但是可空类型排除,(可空类型将会在后面专题有所介绍),如下面的示例:
// 值类型约束
public class samplevaluetype<T> where T : struct
{
public static T Test()
{
return new T();
}
}
在上面代码中,new T()是可以通过编译的,因为T 是一个值类型,而所有值类型都有一个公共的无参构造函数,然而,如果T不约束,或约束为引用类型时,此时上面的代码就会报错,因为有的引用类型没有公共的无参构造函数的。
(3)构造函数类型约束
表示形式为T:new(),如果类型参数有多个约束时,此约束必须为最后指定。确保指定的类型实参有一个公共无参构造函数的非抽象类型,这适用于:所有值类型;所有非静态、非抽象、没有显示声明的构造函数的类(前面括号中已经说了,如果显示声明带参数的构造函数,则编译器就不会为类生成一个默认的无参构造函数,大家可以通过IL反汇编程序查看下的,这里就不贴图了);显示声明了一个公共无参构造函数的所有非抽象类。(注意: 如果同时指定构造器约束和struct约束,C#编译器会认为这是一个错误,因为这样的指定是多余的,所有值类型都隐式提供一个无参公共构造函数,就如定义接口指定访问类型为public一样,编译器也会报错,因为接口一定是public的,这样的做只多余的,所以会报错。)
(4)转换类型约束
表示形式为 T:基类名 (确保指定的类型实参必须是基类或派生自基类的子类)或T:接口名(确保指定的类型实参必须是接口或实现了该接口的类) 或T:U(为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数)。转换约束的例子如下:
声明 |
已构造类型的例子 |
Class Sample<T> where T: Stream |
Sample<Stream>有效的 Sample<string>无效的 |
Class Sample<T> where T: IDisposable |
Sample<Stream >有效的 Sample<StringBuilder>无效的 |
Class Sample<T,U> where T: U |
Sample<Stream,IDispsable>有效的 Sample<string,IDisposable>无效的 |
(5)组合约束(第五种约束就是前面的4种约束的组合)
将多个不同种类的约束合并在一起的情况就是组合约束了。(注意,没有任何类型即时引用类型又是值类型的,所以引用约束和值约束不能同时使用)如果存在多个转换类型约束时,如果其中一个是类,则类必须放在接口的前面。不同的类型参数可以有不同的约束,但是他们分别要由一个单独的where关键字。下面看一些有效和无效的例子来让大家加深印象:
有效:
class Sample<T> where T:class, IDisposable, new();
class Sample<T,U> where T:class where U: struct
无效的:
class Sample<T> where T: class, struct (没有任何类型即时引用类型又是值类型的,所以为无效的)
class Sample<T> where T: Stream, class (引用类型约束应该为第一个约束,放在最前面,所以为无效的)
class Sample<T> where T: new(), Stream (构造函数约束必须放在最后面,所以为无效)
class Sample<T> where T: IDisposable, Stream(类必须放在接口前面,所以为无效的)
class Sample<T,U> where T: struct where U:class, T (类型形参“T”具有“struct”约束,因此“T”不能用作“U”的约束,所以为无效的)
class Sample<T,U> where T:Stream, U:IDisposable(不同的类型参数可以有不同的约束,但是他们分别要由一个单独的where关键字,所以为无效的)
三、利用反射调用泛型方法
下面就直接通过一个例子来演示如何利用反射来动态调用泛型方法的(关于反射的内容可以我博客中的这篇文章:http://www.cnblogs.com/zhili/archive/2012/07/08/AssemblyLoad_and_Reflection.html),演示代码如下:
using System;
using System.Reflection; namespace ReflectionGenericMethod
{
class Program
{
static void Main(string[] args)
{
Test test = new Test();
Type type = test.GetType(); // 首先,获得方法的定义
// 如果不传入BindFlags实参,GetMethod方法只返回公共成员
// 这里我指定了NonPublic,也就是返回私有成员
// (这里要注意的是,如果指定了Public或NonPublic的话,
// 必须要同时指定Instance|Static,否则不返回成员,具体大家可以用代码来测试的)
MethodInfo methodefine = type.GetMethod("PrintTypeParameterMethod", BindingFlags.NonPublic|BindingFlags.Instance|BindingFlags.Static);
MethodInfo constructed; // 使用MakeGenericMethod方法来获得一个已构造的泛型方法
constructed = methodefine.MakeGenericMethod(typeof(string)); // 泛型方法的调用
constructed.Invoke(null,null);
Console.Read();
}
} public class Test
{
private static void PrintTypeParameterMethod<T>()
{
Console.WriteLine(typeof(T));
}
}
}
面代码在调用泛型方法时传入的两个实参都是null,传入第一个为null是因为调用的是一个静态方法, 第二null是因为调用的方法是个无参的方法。 运行结果截图(结果是输出出 类型实参的类型,结果和我们预期的一样):
四、小结
说到这里泛型的内容都已经介绍完了,本系列用了三个专题来介绍泛型,文章内容都基本采用提出疑问(为什么有泛型)到解释疑问,再到深入理解泛型的方式(个人认为这样的讲解方式不错的,如果大家有更好的讲解方式可以在下面留言给我),希望这种方式可以让大家知道泛型的起源,从而更好的理解泛型。后面一专题将和大家介绍了C#4.0中对泛型的改进——泛型的可变性。
泛型专题中用到的所有Demo的源代码:http://files.cnblogs.com/zhili/GeneralDemo.zip
[C# 基础知识系列]专题八: 深入理解泛型(二)的更多相关文章
- [C# 基础知识系列]专题九: 深入理解泛型可变性
引言: 在C# 2.0中泛型并不支持可变性的(可变性指的就是协变性和逆变性),我们知道在面向对象的继承中就具有可变性,当方法声明返回类型为Stream,我们可以在实现中返回一个FileStream的类 ...
- [C# 基础知识系列]专题七: 泛型深入理解(一) (转载)
引言: 在上一个专题中介绍了C#2.0 中引入泛型的原因以及有了泛型后所带来的好处,然而上一专题相当于是介绍了泛型的一些基本知识的,对于泛型的性能为什么会比非泛型的性能高却没有给出理由,所以在这个专题 ...
- [C# 基础知识系列]专题一:深入解析委托——C#中为什么要引入委托
转自http://www.cnblogs.com/zhili/archive/2012/10/22/Delegate.html 引言: 对于一些刚接触C# 不久的朋友可能会对C#中一些基本特性理解的不 ...
- [C# 基础知识系列]专题十六:Linq介绍
转自http://www.cnblogs.com/zhili/archive/2012/12/24/Linq.html 本专题概要: Linq是什么 使用Linq的好处在哪里 Linq的实际操作例子— ...
- [C# 基础知识系列]专题五:当点击按钮时触发Click事件背后发生的事情 (转载)
当我们在点击窗口中的Button控件VS会帮我们自动生成一些代码,我们只需要在Click方法中写一些自己的代码就可以实现触发Click事件后我们Click方法中代码就会执行,然而我一直有一个疑问的—— ...
- [C# 基础知识系列]专题三:如何用委托包装多个方法——委托链 (转载)
引言: 上一专题介绍了下编译器是如何来翻译委托的,从中间语言的角度去看委托,希望可以帮助大家进一步的理解委托,然而之前的介绍都是委托只是封装一个方法,那委托能不能封装多个方法呢?因为生活中经常会听到, ...
- [C# 基础知识系列]专题二:委托的本质论 (转载)
引言: 上一个专题已经和大家分享了我理解的——C#中为什么需要委托,专题中简单介绍了下委托是什么以及委托简单的应用的,在这个专题中将对委托做进一步的介绍的,本专题主要对委本质和委托链进行讨论. 一.委 ...
- 方法字段[C# 基础知识系列]专题二:委托的本质论
首先声明,我是一个菜鸟.一下文章中出现技术误导情况盖不负责 引言: 上一个专题已和大家分享了我懂得的——C#中为什么须要委托,专题中简略介绍了下委托是什么以及委托简略的应用的,在这个专题中将对委托做进 ...
- [C# 基础知识系列]专题四:事件揭秘
转自http://www.cnblogs.com/zhili/archive/2012/10/27/Event.html 引言: 前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听 ...
随机推荐
- vue实战之狗血事件:页面loading效果诡异之事
接上回 想加一个切换路由时,跳出一个loading动画 ,路由加载后就消失 先做了一个loading提示的浮动层的组件,全局注册,在几个路由页面都引入 在vuex里面维护一个变量比如isLoading ...
- linux中使用corntab和shell脚本自动备份nginx日志,按天备份
编写shell脚本,实现nginx日志每天自动备份到指定文件夹! 需要的命令mv , corntab -e(定时任务),shell脚本 这里先说一下corntab: https://www.cnblo ...
- 用代码截图去理解MVC原理
[概述] 看了蒋金楠先生的<Asp.Net Mvc框架揭密>,这本书详细地讲解了mvc的原理,很深奥也很复杂,看了几遍才将就明白了一点.他在第一章用了一个他自己写的mvc框架作为例子,代码 ...
- OE中的bitbake使用
OpenEmbedded是一些脚本(shell和python脚本)和数据构成的自动构建系统. 脚本实现构建过程,包括下载(fetch).解包(unpack).打补丁(patch).config ...
- MXNet深度学习库简介
MXNet深度学习库简介 摘要: MXNet是一个深度学习库, 支持C++, Python, R, Scala, Julia, Matlab以及JavaScript等语言; 支持命令和符号编程; 可以 ...
- 什么是Jupyter Notebook?
Jupyter Notebook, 以前又称为IPython notebook,是一个交互式笔记本, 支持运行40+种编程语言. 可以用来编写漂亮的交互式文档. Linux下, Jupyter Not ...
- poj 2236 加点 然后判断某两点是否连通
题目大意:给你N台电脑,从1-N.一个数字,表示两台计算机的最大通信距离,超过这个距离就无法进行通信.然后分别告诉这些电脑的坐标,接下来有两种操作,第一种O表示这点电脑修好,第二种S,表示测试这两台电 ...
- homestead实现外部局域网络其他主机的访问
转载自:https://blog.csdn.net/u013659696/article/details/78455362 homestead 2.0 MAC环境 修改Homestead目录下的Vag ...
- 000 SpringMVC介绍
1.介绍 2.MVC 模型(Model)封装了应用程序数据,通常它们将由POJO类组成. 视图(View)负责渲染模型数据,一般来说它生成客户端浏览器可以解释HTML输出. 控制器(Controlle ...
- DSP 中关键字extern,cregister,Near ,Far,restrict,volatile
extern:extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.另外,extern也可用来进行链接指定. const: 可以 ...