1.1.1 摘要

图1 C# 泛型介绍

在接触泛型之前,我们编程一般都是使用具体类型(char, int, string等)或自定义类型来定义我们变量,如果我们有一个功能很强的接口,而且我们想把它提取或重构成一个通用的接口,使得该接口不仅仅适用于已定义数据类型,而是适用于更多数据类型,从而方便以后的扩展。

泛型提供上述功能的实现,泛型其实就是提供一个数据类型的抽象层,因为它泛所以抽象,方便了我们代码的重构和提取,我们无需hard-code接口中的数据类型,而是通过一个抽象泛型类型来指定数据类型,所以泛型可以提取出一个通用的接口。接下来让我们通过具体的例子说明什么是泛型。

1.1.2 正文

1.1.1.1 没有泛型的C# 1.0

相信许多人都使用过栈或者其他经典数据结构,而且对于栈的FILO都是娴熟于心了。假设现在要我们定义一个栈,用来存放具体数据类型,例如:int 数据,OK让我们来实现自定义栈。

/// <summary>
/// A custom stack1.
/// </summary>
public class CustomStack1
{private int _stackPointer = 0;private int[] _stackArray;public void Push(int item){Concrete implemention.}public int Pop(){Concrete implemention.}
}

为了简洁没有完全遵守OOP的原则,这里没有使用属性和给出方法的具体实现,现在我们有了第一版可以存放int数据类型的栈。但如果需求改变保存数据类型增加string。那么我们可以怎样实现呢?没错我们只要增加一个类型为string的栈就OK了。

/// <summary>
/// A custom stack2.
/// </summary>
public class CustomStack2
{private int _stackPointer = 0;private string[] _stackArray;public void Push(int item){Concrete implemention.}public string Pop(){Concrete implemention.}
}

很快我们就完成了第二版本的栈了,这看上去的确很简单而且实现起来很快只不过是ctrl + c和ctrl + v,但我们的代码真的那么简单好维护吗?想想其实危机四伏。我们能不能一开始就把保存的数据类型定义成为一个通用的类型呢(抽象数据类型)?----邪恶的object。

/// <summary>
/// A custom stack.
/// </summary>
public class CustomStack
{private int _stackPointer = 0;private object[] _stackArray;public void Push(object item){Concrete implemention.}public object Pop(){Concrete implemention.}
}

现在我们实现了第三版的栈了,看上去我们好像找到了通用的解决方法,想想我们要注意一点是如果我们保存和取出数据类型为值类型时,值类型要进行boxing和unboxing操作(《.NET值类型和引用类型101》),而且还有进行数据类型检测。如果数据量不大我们可能察觉不到性能变化,但数据量大hehehe…。这就是为什么我们要使用泛型的原因之一,但更重要的原因是在进行boxing和unboxing时,我们需要提供更多信息给编译器,编译时再根据我们提供的信息进行类型检测。

1.1.1.2 C#中的泛型

现在我们知道了.NET为什么要提供泛型,现在让我们学习什么是泛型和怎样实现吧!在C# 2.0中提供了泛型这一机制,但这可不是什么新奇的技术,因为早在C++和Java都提供泛型机制。

C#中提供五种泛型分别是:classes, structs, interfaces, delegates, and methods。

  • 泛型类

也许有人说没有使用过C++的模板或Java的泛型,而且不了解C#中的泛型,真的是这样吗?其实我们经常都在使用泛型,相信很多人都使用过Dictionary<TKey,TValue>,没错这就是C#中提供的泛型字典,接下来我们将介绍自定义泛型类CustomStack。

图2泛型类

/// <summary>
/// A custom generic stack.
/// </summary>
public class CustomStack<T>
{private int _stackPointer = 0;private T[] _stackArray;public void Push(T item){Concrete implemention.}public T Pop(){Concrete implemention.}
}

前面给出了泛型类CustomStack实现,我们发现只需在类名后添加<T>,并且把具体数据类型改为抽象的泛型类型T,现在我们就定义了一个简单泛型类,我们可以通过以下对比一下非泛型和泛型实现的区别。

图3非泛型和泛型类定义

通过上图我们发现泛型类CustomStack和非泛型类CustomStack在实现上没有太大的区别,我们只需把具体类型换成T就OK了,接下来我们将进入泛型的进阶学习。

  • 泛型委托

关于委托大家可以阅读《.NET 中的委托》和《.NET中的委托和事件(续)》,现在让我们学习一下泛型委托。

图4泛型委托的定义

上图给出了泛型委托的定义,我们要注意Type parameters包括泛型参数和返回类型。随着C#3.0 Linq特性的引用,泛型委托的使用得到了极大地扩展,让我们通过具体代码讲讲如何使用泛型委托。

首先我们定义一个泛型委托,然后再定义一个Calc类,接着在Calc里定义两个方法分别是Add() 和Divide() 如下:

/// <summary>
/// Define generic delegate.
/// </summary>
public delegate TR CalcMethod<T1, T2, TR>(T1 x, T2 y);public class Calc
{static private double sum;/// <summary>/// Gets or sets the sum./// </summary>/// <value>/// The sum./// </value>static public double Sum{get { return sum; }set { sum = value; }}/// <summary>/// Adds the specified x./// </summary>/// <param name="x">The x.</param>/// <param name="y">The y.</param>/// <returns></returns>static public string Add(int x , int y){Sum = x + y;return Sum.ToString();}/// <summary>/// Divides the specified x./// </summary>/// <param name="x">The x.</param>/// <param name="y">The y.</param>/// <returns></returns>static public string Divide(int x, int y){if (y == 0){return "除数不能为零";}Sum = x * 1.0 / y;return Sum.ToString();}} 

接下来我们需要定义一个委托变量,而后将委托变量和具体调用方法绑定就OK了。

Initialize engeric delegate instance to Add method.
CalcMethod<int, int, string> calcMethod =new CalcMethod<int, int, string>(Calc.Add);
  • 类型约束

前面例子我们并没有对泛型类型进行限制,由于T是一个抽象类型,有时我们必须限制T是哪种类型,例如我们可以定义CustomStack<int>,CustomStack<double>或CustomStack<string>类型的栈,但有时我们必须限制泛型类型。这时我们需要使用类型约束,引入关键字----where。

C#中提供五种类型约束(类名,接口名,引用类型,值类型和构造函数),我们可以通过使用不同的类型约束来限定泛型类型。

类型约束

约束描述

ClassName

T只能是该类或继承该类的子类

InterfaceName

T只能是该接口或实行该接口的类型

struct

T是任意值类型

class

T是任意引用类型(如classes, arrays, delegates, and interfaces等)

new()

T是包含公有无参构成函数的任意类型

表1类型约束

通过表1我们初步地了解不同类型约束之间的区别,现在让我们通过具体的例子来看看它们使用范围和区别。

假设我们定义了一个泛型结构体struct RefSample<T> where T : class,现在考考大家以下例子符合类约束的有(提示值类型和引用类型区别):

A.  RefSample<IDisposable>

B.  RefSample<string>

C. RefSample<int[]>

D. RefSample<Guid>

E.  RefSample<int>

激动人心的时刻又到了现在让我们快快公布答案吧!答案是ABC,因为该泛型的约束是引用类型约束,如果大家还不清楚请看一下引用类型约束的定义。

通过类约束例子,我们举一反三值类型约束就是约束类型为值类型。

OK接下来让我们介绍构造函数约束。在介绍构造函数约束之前,让我们回顾一下符合构造函数约束的条件:任意值类型,任意非静态非抽象无明确定义构造函数的类和任意非抽象有明确定义无参构造函数的类。这句话很别扭,让我们通过具体例子讲解一下。

 Define generic methodWhich has constructor type constraints
public T CreateInstance<T>() where T : new()
{
return new T();
}CreateInstance<int>();
CreateInstance<object>();
CreateInstance<string>();

现在我们定义了一个泛型方法并且添加构造函数约束,调用CreateInstance<int>() 和 CreateInstance<object>() 成功,但调用CreateInstance<string>() 失败,这由于String类没有提供无参构造函数。也许有人会问:“我们很难判断每种类型是否包含默认构无参造函数”,确确是这样但我们可以记住的是C#规定值类型提供默认无参构造函数。

  • 组合约束

前面的泛型例子都是只使用一种类型约束,但有时候我们要使用多种类型约束,这时我们就可以使用组合约束,在使用约束的时我们要注意约束的先后次序,约束次序如下:

图5约束次序

通过上图我们发现ClassName,class和struct是主要约束,接口名是第二约束,最后是构造函数约束。OK现在我们对组合约束有了初步地了解,那么让我们通过以下例子加深理解。

A. class Sample<T> where T : class, struct

B. class Sample<T> where T : Stream, class

C. class Sample<T> where T : new(), Stream

D. class Sample<T> where T : IDisposable, Stream

E. class Sample<T> where T : XmlReader, IComparable, IComparable

F. class Sample<T,U> where T : struct where U : class, T

G. class Sample<T,U> where T : Stream, U : IDisposable

上面给出错误使用组合约束的例子(注:A~E单类型组合约束,FG多类型组合约束),请大家指出约束错误的原因,如果不清楚错误的原因大家回忆一下约束定义。

1.1.3 总结

本文注意介绍了C# 中泛型的基础知识给出自己的总结,而且这不是C# 泛型的全部,如果大家在看完本文之后希望进一步学习泛型这才是我的目的。

泛型的优点:

  • 编译时进行类型检测,减少运行时异常InvalidCastException。
  • 泛型使用强数据类型。如我们实例一个CustomStack<CustomStruct>对象objStack,通过调用Push()方法成功把CustomStruct压入栈中,而objStack.Push(“Stack”)失败。
  • 值类型无需进行boxing和unboxing操作。例如前面的泛型Push()和Pop()方法存放和取出值类型时无需进行boxing和unboxing操作。
  • 减少代码量。如:我们定义一个泛型栈,就不用像前面例子那样分别定义int和string类型的栈。
  • 程序性能提高,因为无需类型转换,从而减少类型检测。
  • 使用泛型减少内存消耗。因为无需进行boxing操作,不用重新分配堆空间。
  • 代码可读性更强。

很多人喜欢将C# ,C++ 和Java中的泛型进行对比,而且也有很多相关的讨论,在这里我也给出自己的想法,我觉得C++ 的泛型功能的确比C# 和Java都要强大。

[作者]:JK_Rush
[出处]:http://www.cnblogs.com/rush/
[本文基于]: 署名-非商业性使用 3.0 许可协议发布,欢迎转载,演绎,但是必须保留本文的署名 JK_Rush (包含链接),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系 。

.NET 中的泛型 101相关推荐

  1. 5.在MVC中使用泛型仓储模式和工作单元来进行增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

  2. Java中创建泛型数组

    Java中创建泛型数组 使用泛型时,我想很多人肯定尝试过如下的代码,去创建一个泛型数组 T[] array = new T[]; 当我们写出这样的代码时编译器会报Cannot create a gen ...

  3. 技术图文:C#语言中的泛型 II

    C#语言中的泛型 II 知识结构: 6. 泛型接口 泛型类与泛型接口结合使用是很好的编程习惯,比如用IComparable<T>而非IComparable,以避免值类型上的装箱和拆箱操作. ...

  4. 技术图文:C#语言中的泛型 I

    C#语言中的泛型 I 知识结构: 1. 泛型概述 泛型广泛应用于容器(Collections)和对容器操作的方法中. 从 .NET Framework2.0 开始,微软提供了一个新的命名空间Syste ...

  5. Java中的泛型 --- Java 编程思想

    前言 ​ 我一直都认为泛型是程序语言设计中一个非常基础,重要的概念,Java 中的泛型到底是怎么样的,为什么会有泛型,泛型怎么发展出来的.通透理解泛型是学好基础里面中非常重要的.于是,我对<Ja ...

  6. java中什么泛型_【原创】java中的泛型是什么,有什么作用

    泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方 ...

  7. 【笔记】JAVA中的泛型和反射

    泛型 Java的泛型是如何工作的?什么是类型擦除? ----泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息.例如List在运行时仅用一个Lis ...

  8. dart系列之:dart类中的泛型

    文章目录 简介 为什么要用泛型 怎么使用泛型 类型擦除 泛型的继承 泛型方法 总结 简介 熟悉JAVA的朋友可能知道,JAVA在8中引入了泛型的概念.什么是泛型呢?泛型就是一种通用的类型格式,一般用在 ...

  9. SSH整合中,使用父action重构子类action类.(在父类中获取子类中的泛型对象)

    import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type;import com.opensymphony.xw ...

最新文章

  1. 上海交大张拳石:漂在零丁洋里的体系,神经网络的博弈交互解释性
  2. 一个失败的操作系统MULTICS
  3. org.hibernate.NonUniqueObjectException 异常处理
  4. java工程师linux命令,这篇文章就够了
  5. python3moduleoftheweek中文_[翻译]Python Module of The Week: Counter
  6. mysql存储引擎优化参数
  7. LeetCode 54. Spiral Matrix
  8. 小程序开发 js里面array操作的方法列表。
  9. Timeline Storyteller 现已加入自定义图表库
  10. opencv_3.4.2_vc14_vc15.exe下载
  11. c语言timer linux 回调函数_SetTimer 与 回调函数
  12. js导出excel(超简单)
  13. xhEditor技术手册
  14. word论文排版,页码和页眉
  15. 如何将电脑(网线)网络共享给iPhone苹果手机(不需要数据线)
  16. ACM赛后总结2018.09.23
  17. 历史经验之解决vMix22闪退的办法(亲测管用)
  18. 2015年最新苹果开发者账号注册流程详解
  19. jscese 知其白 守其黑 為天下式 __Read The Fucking Source Code的博客汇总
  20. 【Pytorch深度学习实践】B站up刘二大人之SoftmaxClassifier-代码理解与实现(8/9)

热门文章

  1. 面试题——20190717
  2. IP通信基础 实验三
  3. 图片在线转换base64
  4. JQuery中this指向
  5. 测试对bug如何分析和定位
  6. HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别(转)
  7. Jmeter_初步认识随笔
  8. 分珠(dfs+并查集)
  9. ExtJs4 笔记 Ext.tab.Panel 选项卡
  10. [转]网页栅格系统研究(2):蛋糕的切法