一、什么是泛型?

泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个非常重要的新功能。

我们在编程程序时,经常会遇到功能非常相似的模块,只是它们处理的数据不一样。但我们没有办法,只能分别写多个方法来处理不同的数据类型。这个时候,那么问题来了,有没有一种办法,用同一个方法来处理传入不同种类型参数的办法呢?泛型的出现就是专门来解决这个问题的,可以看出,微软还是很贴心的。

二、为什么要使用泛型?

接下来我们来看一段代码。

public class GenericClass
{
public void ShowInt(int n)
{
Console.WriteLine("ShowInt print {0},ShowInt Parament Type Is {1}",n,n.GetType());
}
public void ShowDateTime(DateTime dt)
{
Console.WriteLine("ShowDateTime print {0},ShowDateTime Parament Type Is {1}", dt, dt.GetType());
}
public void ShowPeople(People people)
{
Console.WriteLine("ShowPeople print {0},ShowPeople Parament Type Is {1}", people, people.GetType());
}
}
 static void Main(string[] args)
{
GenericClass generice = new GenericClass();
generice.ShowInt(11);
generice.ShowDateTime(DateTime.Now);
generice.ShowPeople(new People { Id = 11, Name = "Tom" }); Console.ReadKey();
}

 

显示结果:

我们可以看出这三个方法,除了传入的参数不同外,其里面实现的功能都是一样的。在1.1版的时候,还没有泛型这个概念,那么怎么办呢。就有人想到了OOP三大特性之一的继承,我们知道,C#语言中,所有类型都源自同一个类型,那就是object。

    public class GenericClass
{
public void ShowObj(object obj)
{
Console.WriteLine("ShowObj print {0},ShowObj Parament Type Is {1}", obj, obj.GetType());
}
}
static void Main(string[] args)
{
Console.WriteLine("*****************object调用*********************");
generice.ShowObj(11);
generice.ShowObj(DateTime.Now);
generice.ShowObj(new People { Id = 11, Name = "Tom" }); Console.ReadKey();
}

显示结果:

我们可以看出,目地是达到了。解决了代码的可读性,但是这样又有个不好的地方了,我们这样做实际上是一个装箱拆箱操作,会损耗性能。

终于,微软在2.0的时候发布了泛型。接下来我们用泛型方法来实现该功能。

三、泛型类型参数

在使用泛型方法之前,我们先来了解下有关于泛型的一些知识。

在泛型类型或方法定义中,类型参数是在其实例化泛型类型的一个变量时,客户端指定的特定类型的占位符。 泛型类( GenericList<T>)无法按原样使用,因为它不是真正的类型;它更像是类型的蓝图。 若要使用 GenericList<T>,客户端代码必须通过指定尖括号内的类型参数来声明并实例化构造类型。 此特定类的类型参数可以是编译器可识别的任何类型。 可创建任意数量的构造类型实例,其中每个使用不同的类型参数,如下所示:

GenericList<float> list1 = new GenericList<float>();
GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();
GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();

在 GenericList<T> 的每个实例中,类中出现的每个 T 在运行时均会被替换为类型参数。 通过这种替换,我们已通过使用单个类定义创建了三个单独的类型安全的有效对象。

三、泛型约束

定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的几种类型施加限制。 如果客户端代码尝试使用约束所不允许的类型来实例化类,则会产生编译时错误。 这些限制称为约束。 通过使用 where 上下文关键字指定约束。 下表列出了六种类型的约束:

where T:结构(类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。)

class MyClass<U>
where U : struct///约束U参数必须为“值 类型”
{ } public void MyMetod<T>(T t)
where T : struct
{
}

where T:类(类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。)

class MyClass<U>
where U : class///约束U参数必须为“引用类型”
{ } public void MyMetod<T>(T t)
where T : class
{
}

where T:new()(类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。)

class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
// ...
}

where T:<基类名>(类型参数必须是指定的基类或派生自指定的基类。)

public class Employee{}

public class GenericList<T> where T : Employee

where T:<接口名称>(类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。)

 /// <summary>
/// 接口
/// </summary>
interface IMyInterface
{
} /// <summary>
/// 定义的一个字典类型
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TVal"></typeparam>
class Dictionary<TKey, TVal>
where TKey : IComparable, IEnumerable
where TVal : IMyInterface
{
public void Add(TKey key, TVal val)
{
}
}

where T:U(为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。也就是说T和U的参数必须一样)

class List<T>
{
void Add<U>(List<U> items) where U : T {/*...*/}
}

以上就是对六种泛型的简单示例,当然泛型约束不仅仅适用于类,接口,对于泛型方法,泛型委托都同样适用。

三、泛型方法

public class GenericClass
{
public void ShowT<T>(T t)
{
Console.WriteLine("ShowT print {0},ShowT Parament Type Is {1}", t, t.GetType());
}
}
static void Main(string[] args)
{
Console.WriteLine("*****************泛型方法调用*********************");
generice.ShowT<int>(11);
generice.ShowT<DateTime>(DateTime.Now);
generice.ShowT<People>(new People { Id = 11, Name = "Tom" }); Console.ReadKey();
}

显示结果:

也是一样的,现在终于实现了我们想要达到的效果了。我们可以看出,无论是什么方式调用,最后我们获取出来的类型都是原始类型。我们知道,用object获取是利用了继承这一特性,当编译器编译的时候,我们传入的参数会进行装箱操作,当我们获取的时候又要进行拆箱操作,这个方法会损耗性能 。那么泛型方法实现的原理又是怎样的呢?首先,我们要知道,泛型是一个语法糖,在我们调用泛型方法,编译器进行编译时,才会确定传入的参数的类型,从而生成副本方法。这个副本方法与原始方法一法,所以不会有装箱拆箱操作,也就没有损耗性能这回事了。

四、泛型类

泛型类封装不特定于特定数据类型的操作。

通常,创建泛型类是从现有具体类开始,然后每次逐个将类型更改为类型参数,直到泛化和可用性达到最佳平衡。

创建自己的泛型类时,需要考虑以下重要注意事项:

  • 要将哪些类型泛化为类型参数。

通常,可参数化的类型越多,代码就越灵活、其可重用性就越高。 但过度泛化会造成其他开发人员难以阅读或理解代码。

  • 要将何种约束(如有)应用到类型参数

其中一个有用的规则是,应用最大程度的约束,同时仍可处理必须处理的类型。 例如,如果知道泛型类仅用于引用类型,则请应用类约束。 这可防止将类意外用于值类型,并     使你可在   T  上使用  as  运算符和检查 null 值。      

  • 是否将泛型行为分解为基类和子类。

    因为泛型类可用作基类,所以非泛型类的相同设计注意事项在此也适用。 请参阅本主题后文有关从泛型基类继承的规则。

  • 实现一个泛型接口还是多个泛型接口。

class BaseNode { }
class BaseNodeGeneric<T> { } // concrete type
class NodeConcrete<T> : BaseNode { } //closed constructed type
class NodeClosed<T> : BaseNodeGeneric<int> { } //open constructed type
class NodeOpen<T> : BaseNodeGeneric<T> { }

五、泛型接口

  • 定义一个泛型接口:
interface IMyGenericInterface<T>
{
}
  • 一个接口可定义多个类型参数,如下所示:
interface IMyGenericInterface<TKey,TValue>
{
}
  • 具体类可实现封闭式构造接口,如下所示:
interface IBaseInterface<T> { }

class SampleClass : IBaseInterface<string> { }//如果T有约束,那么string类型必须得满足T的约束

六、泛型委托

委托可以定义它自己的类型参数。 引用泛型委托的代码可以指定类型参数以创建封闭式构造类型,就像实例化泛型类或调用泛型方法一样,如以下示例中所示:

 class Program
{
static void Main(string[] args)
{
Del<int> m1 = new Del<int>(Notify);
m1.Invoke(1111);
Del<string> m2 = new Del<string>(Notify);
m2.Invoke("字符串"); Console.ReadKey();
} public delegate void Del<T>(T item);
public static void Notify(int i) { Console.WriteLine("{0} type is {1}", i,i.GetType()); }
public static void Notify(string str) { Console.WriteLine("{0} type is {1}", str, str.GetType()); } }

运行结果:

七、泛型代码中的默认关键字:Default

在泛型类和泛型方法中产生的一个问题是,在预先未知以下情况时,如何将默认值分配给参数化类型 T:

  • T 是引用类型还是值类型。

  • 如果 T 为值类型,则它是数值还是结构。

给定参数化类型 T 的一个变量 t,只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。解决方案是使用 default 关键字,此关键字对于引用类型会返回空,对于数值类型会返回零。对于结构,此关键字将返回初始化为零或空的每个结构成员,具体取决于这些结构是值类型还是引用类型。

namespace MyGeneric
{
class Program
{
static void Main(string[] args)
{
object obj1=GenericToDefault<string>();
object obj2 = GenericToDefault<int>();
object obj3 = GenericToDefault<StructDemo>();
Console.ReadKey();
}
public static T GenericToDefault<T>()
{
return default(T);
}
}
public struct StructDemo
{
public int Id { get; set; }
public string Name { get; set; }
}
}

运行结果:

C#指南,重温基础,展望远方!(13)C#泛型的更多相关文章

  1. 【重温基础】17.WebAPI介绍

    本文是 重温基础 系列文章的第十七篇. 今日感受:挑战. 系列目录: [复习资料]ES6/ES7/ES8/ES9资料整理(个人整理) [重温基础]1-14篇 [重温基础]15.JS对象介绍 [重温基础 ...

  2. 【重温基础】15.JS对象介绍

    从这篇文章开始,复习 MDN 中级教程 的内容了,在初级教程中,我和大家分享了一些比较简单基础的知识点,并放在我的 [Cute-JavaScript]系列文章中. 关于[Cute-JavaScript ...

  3. chrome调试工具高级不完整使用指南(基础篇)

    一.前言 本文记录的是作者在工作上面对chrome的一些使用和情况的分析分享,内容仅代表个人的观点.转发请注明出处(http://www.cnblogs.com/st-leslie/),谢谢合作 二. ...

  4. JVM基础系列第13讲:JVM参数之追踪类信息

    我们都知道 JVM 在启动的时候会去加载类信息,那么我们怎么得知他加载了哪些类,又卸载了哪些类呢?我们这一节就来介绍四个 JVM 参数,使用它们我们就可以清晰地知道 JVM 的类加载信息. 为了方便演 ...

  5. Android零基础入门第13节:Android Studio配置优化,打造开发利器

    原文:Android零基础入门第13节:Android Studio配置优化,打造开发利器 是不是很多同学已经有烦恼出现了?电脑配置已经很高了,但是每次运行Android程序的时候就很卡,而且每次安装 ...

  6. 【重温基础】16.JSON对象介绍

    本文是 重温基础 系列文章的第十六篇. 今日感受:静. 系列目录: [复习资料]ES6/ES7/ES8/ES9资料整理(个人整理) [重温基础]1-14篇 [重温基础]15.JS对象介绍 本章节复习的 ...

  7. 【重温基础】11.Map和Set对象

    本文是 重温基础 系列文章的第十一篇. 今日感受:注意身体,生病花钱又难受. 系列目录: [复习资料]ES6/ES7/ES8/ES9资料整理(个人整理) [重温基础]1.语法和数据类型 [重温基础]2 ...

  8. Xamarin图表开发基础教程(13)OxyPlot框架支持的其它图表

    Xamarin图表开发基础教程(13)OxyPlot框架支持的其它图表 除了以上提到的图表外,OxyPlot组件还包含了6种类型的其它图表,分别为等高线图.箱线图.饼图.热图.散点图和散点误差图,如图 ...

  9. C# 基础知识系列-13 常见类库(三)

    0. 前言 在<C# 基础知识系列- 13 常见类库(二)>中,我们介绍了一下DateTime和TimeSpan这两个结构体的内容,也就是C#中日期时间的简单操作.本篇将介绍Guid和Nu ...

随机推荐

  1. CentOS下的RPM包制作教程收集(rpmbuild)

    http://www.jinbuguo.com/redhat/rpmbuild.html(中文手册) https://www.linuxidc.com/Linux/2016-09/134955.htm ...

  2. extjs form textfield的隐藏方法

    只需将textfield的hidden和hideLabel配置为true就可以了.只设置hidden:true时会显示出来一个:的标签.     this.formpanel = new Ext.Fo ...

  3. Digital variable resistor compensates voltage regulator

    A variable resistor that integrates a programmable, temperature-indexed look-up table can compensate ...

  4. 引用计数的cocos2dx对象内存管理和直接new/delete box2d对象内存管理冲突的解决方法

    转载请注明: http://blog.csdn.net/herm_lib/article/details/9316601 项目中用到了cocos2dx和box2d,cocos2dx的内存是基于引用计数 ...

  5. nodejs之处理GET请求

    一个简单的httpserver.接收get请求,并返回解析之后的数据. 以下是服务的代码: var http = require("http"); var url = requir ...

  6. Linux使用RPM安装软件

    什么是RMP? RPM 的为Redhat Package Manager (RPM软件包管理器)的缩写. RPM包,这种软件包就像windows的EXE安装文件一样,各种文件已经编译好,并打了包,哪个 ...

  7. 第一篇 对Javascript中原型的深入理解

      理解原型对象 在Javascript中不管什么时候,仅仅要创建一个新的函数,就会依据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象(这个对象的用途是包括能够有特定 ...

  8. 虚拟机安装android

    通过 虚拟机VirtualBox安装Android x86 4.0系统. Android x86是一个致力于让android运行在x86架构机器上的民间组织搞的项目,目前在世界上有很多人加入了它,虽然 ...

  9. [翻译] Blocks and Variables

    Blocks and Variables https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Blocks/A ...

  10. iOS:下拉刷新控件UIRefreshControl的详解

    下拉刷新控件:UIRefreshControl 1.具体类信息: @interface UIRefreshControl : UIControl //继承控制类 - (instancetype)ini ...