.NET泛型解析(上)

【1】:泛型介绍

泛型是C#2.0中一个重要的新特性,泛型是CLR和编程语言提供的一种特殊机制,它支持另一种形式的代码重用。泛型通常用与集合以及作用于集合的方法一起使用,当然也可以单独使用.

C#是一种强类型的语言,在泛型没有被提出之前,我们在使用集合的代码的时候,每次对其进行转换都需要隐式的强制转换,我们都知道所有对象的最终基类是object,我们在每次使用object的时候,无论是变换什么类型都要对其进行强制转换。

那么有了泛型之后,使用泛型我们就无需进行转换,因为泛型根据接收者传入的参数类型,直接将每个类型更改为所传入的参数类型.

一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。 创建您自己的泛型类时,需要特别注意以下事项:

  • 将哪些类型通用化为类型参数。

    通常,能够参数化的类型越多,代码就会变得越灵活,重用性就越好。 但是,太多的通用化会使其他开发人员难以阅读或理解代码。

  • 如果存在约束,应对类型参数应用什么约束

    一条有用的规则是,应用尽可能最多的约束,但仍使您能够处理必须处理的类型。 例如,如果您知道您的泛型类仅用于引用类型,则应用类约束。 这可以防止您的类被意外地用于值类型,并允许您对 T 使用 as 运算符以及检查空值。

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

    由于泛型类可以作为基类使用,此处适用的设计注意事项与非泛型类相同。 请参见本主题后面有关从泛型基类继承的规则。

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

    例如,如果您设计一个类,该类将用于创建基于泛型的集合中的项,则可能必须实现一个接口,如 IComparable,其中 T 是您的类的类型。

【2】:泛型的表示方式

泛型可以为引用类型和值类型还有接口和委托,FCL( DLL程序集,包含了.NET框架下的各种DLL )类中定义了一个泛型列表,用来管理一个对象集合,如果我们想要使用这个泛型列表,可以在使用时指定具体数据类型。

泛型的表示为 “T” 如:List<T>, T 表示一个未指定的数据类型,我们可以看一下FCL类中泛型的引用定义:

System.Collections.Generic 命名空间包含定义泛型集合的接口和类,泛型集合允许用户创建强类型集合,它能提供比非泛型强类型集合更好的类型安全性和性能。

创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡

【3】:泛型的好处

1 : 使代码更加的简洁,清晰

从前面我们也提到了,泛型具备可重用性 , 减少我们代码量, 使我们的程序更容易开发和维护,举例 :

实例 1 : 在从数据库中获取数据库的时候,我们经常会返回一个DataTable类型,然后将其转换为List集合. 那么如果没有泛型的话,我会一般会这样来做.

 1 public List<TrainingUser>GetTrainingUser(string userId)
 2         {
 3             DataTable dt =
 4                      SqliteHelper.ExecuteDataset(System.Data.CommandType.Text,
 5                         @"
 6                         SELECT DISTINCT UserId,TrainingId FROM TRAININGUSER AS TU
 7                         INNER JOIN [USER] AS U
 8                          ON U.ID = TU.USERID
 9                         JOIN [TRAINING] AS T
10                         ON T.ID = TU.TRAININGID
11                         WHERE U.ID = ‘"+userId+"‘ AND T.ENDTIME > DATETIME(‘now‘, ‘localtime‘) AND T.StartTime <= DATETIME(‘now‘, ‘localtime‘) ;").Tables[0];
12             return DataTableToList(dt);
13         }
14
15         private List<TrainingUser> DataTableToList(DataTabledt)
16         {
17             List<TrainingUser> list = new List<TrainingUser>();
18             if(dt. Rows.Count > 0 )
19             {
20                 foreach (DataRow row in dt .Rows)
21                 {
22                     TrainingUser trainingUser = new TrainingUser();
23                     if(row["UserId" ] != null)
24                     {
25                         trainingUser .UserId = row["UserId"].ToString();
26                     }
27                     if(row["TrainingId" ] != null)
28                     {
29                         trainingUser.TrainingId = row["TrainingId"].ToString();
30                     }
31                     list.Add(trainingUser);
32                 }
33             }
34             return list;
35         }

在方法DataTableToList中,我们传入了一个DataTable的对象,然后在去循环遍历每一行的对象值从而去赋值给TrainingUser类型对象,这只是其中的一个方法,如果我们的类型还有 Training/User/Project等类型的话,我们是不是就要写很多如同DataTableToList这样的方法呢? 这就体现出了这样的方式,会造成代码的冗余以及维护不便问题,那么我们使用泛型来解决

实例 2 : 使用泛型使上面的代码更见的清晰,简洁

 1  public static List<T> ToList1<T>(DataTable dt) whereT : class, new()
 2         {
 3             var prlist =new List<PropertyInfo>();
 4             Type type = typeof(T);
 5             Array.ForEach(
 6                 type.GetProperties(),
 7                 p =>
 8                 {
 9                     if(dt.Columns.IndexOf(p.Name) !=-1)
10                     {
11                         prlist.Add(p);
12                     }
13                 });
14             var oblist = new List<T>();
15
16             // System.Data.SqlTypes.
17             foreach(DataRow row in dt.Rows)
18             {
19                 var ob = new T();
20                 prlist.ForEach(
21                     p =>
22                     {
23                         if(row[p.Name] != DBNull.Value)
24                         {
25                             p.SetValue(ob, row[p.Name], null);
26                         }
27                     });
28                 oblist.Add(ob);
29             }
30
31             return oblist;
32         }

在上面的这个方法中,我们定义了一个泛型方法,内部实现中是使用了反射的原理,将DataTable转换为了List(反射后续随笔中总结,此处只关注泛型部分即可),我们定义了一个静态的返回值为List<T> ,前面我们说过 T : 代表任意类型(枚举除外),ToList1<T>,说明我们在调用这个方法的时候,同时要赋予方法名一个类型值,这个类型要和它的返回值类型一致(泛型是类型安全的),Where : 用于限制T的条件 ,例如 where T : class,new() 表示 T 只能是一个类,或者一个类型对象,那么我们在调用的时候就可以这样来

 1 public List<TrainingUser>GetTrainingIdByUserId(string userId)
 2         {
 3               List<TrainingUser> trainingUserList =  DataTableHelper.ToList1<TrainingUser>(
 4                     SqliteHelper.ExecuteDataset(System.Data.CommandType.Text,
 5                         @"
 6                         SELECT DISTINCT UserId,TrainingId FROM TRAININGUSER AS TU
 7                         INNER JOIN [USER] AS U
 8                          ON U.ID = TU.USERID
 9                         JOIN [TRAINING] AS T
10                         ON T.ID = TU.TRAININGID
11                         WHERE U.ID = ‘"+ userId +"‘ AND T.ENDTIME > DATETIME(‘now‘, ‘localtime‘) AND T.StartTime <= DATETIME(‘now‘, ‘localtime‘) ;").Tables[0]);
12               return trainingUserList ;
13         }

代码中的DataTableHelper.ToList1<TrainingUser> 即为我们刚才所写的一个泛型方法,这样我们可以看到,ToList<T> 传入的类型为TrainingUser,同时接收者为:List<T> = List<TrainingUser> ,

这样即便我们后续还有Training/User/Project等其他的类型,我们都可以直接使用DataTableHelper.ToList1<T>(DataTable dt) 来进行类型转换.

2 : 提升程序的性能

泛型与非泛型相比较而言,性能要好一些,这是为什么? 首先,泛型的类型是由调用者(接收者),去直接赋值的(类型安全的), 那么就不会存在类型转换的问题,其次, 泛型减少了装箱和拆箱的过程.

实例 3 : 对于值类型泛型与非泛型的性能比较

 1         private static void ListTest()
 2         {
 3             List<int>list = new List<int>();
 4             for(inti = 0; i < 100; i++)
 5             {
 6                 list.Add(i);
 7                 int a = list[i];
 8             }
 9             list =null;
10         }
11         private static void ArrListTest()
12         {
13             ArrayList arr = new ArrayList();
14             for(inti = 0; i <100; i++)
15             {
16                 arr.Add(i);
17                 int s = (int)arr[i];
18             }
19             arr = null;
20         }
21
22              Stopwatch sw = new Stopwatch();
23             sw.Start();
24             ListTest();
25             Console.WriteLine(" 使用泛型List执行值类型方法历时 :  "+ sw.Elapsed.ToString());
26             sw.Stop();
27
28             Stopwatch sw1 = new Stopwatch();
29             sw1.Start();
30             ArrListTest();
31             Console.WriteLine(" 使用非泛型ArrayList执行值类型方法历时 :  "+ sw1.Elapsed.ToString());
32             sw1.Stop();
33             Console.ReadLine();

通过循环 100 来比较,结果为 :

我们可以看到非泛型的时间要比泛型的时间多出0.0000523秒,泛型比非泛型的时间要多出一些, 那么我们将数值改动一下改为循环 1000次.得出结果为 :

泛型比非泛型执行的时间要短0.0000405秒

我们将时间在改动一下,改为 100000呢?结果为 :

这次差距为 0.0054621 并且随着执行次数的增长,非泛型相比泛型的时间会逐步的增加,

通过反编译我们也能看出 :

泛型:

非泛型

从编译中我们也能看出泛型方法中,接收的为Int32,非泛型为Object,其次泛型不会进行装箱和拆箱操作,非泛型每次执行都要进行装箱和拆箱操作.

3 : 类型安全

在实例1 , 2 ,3 中我们都有备注说明,泛型的发送着必须要和接收者进行一致,否则会报异常 ,例如 :

实例 4 :

将一个泛型算法应用于一个具体的类型时,编译器和CLR能理解开发人员的意图,并保证只有与指定数据类型兼容的对象才能随同算法使用,若试图使用不兼容类型的一个对象,会造成编译时错误,或者运行时抛出异常

此篇至此,下篇主要知识点 :

1、泛型接口

2、泛型委托

3、泛型约束(主要约束,次要约束,构造器约束)

4、泛型类型转型

5、泛型和反射

6、泛型和属性

参考资料 :

CLR C# Via

深入理解C#

https://msdn.microsoft.com/zh-cn/library/sz6zd40f.aspx

https://msdn.microsoft.com/zh-cn/library/0x6a29h6.aspx

温馨提示 :  知识点重温,不断总结,思考, 也是一种阶段性提高,希望能帮到在读的你.

时间: 2024-10-06 00:24:44

.NET泛型解析(上)的相关文章

Java泛型解析(04):约束和局限性

Java泛型解析(04):约束和局限性 前两节,认识和学习了泛型的限定以及通配符,初学者可能需要一些时间去体会到泛型程序设计的好处和力量,特别是想成为库程序员的同学就需要下去体会通配符的运用了,应用程序员则需要掌握怎么使用泛型,这里针对泛型的使用中的约束和局限性做一个介绍性的讲解. 不能用基本类型实例化类型参数 这个很好理解,实例化泛型类时泛型参数不能使用基本类型,如List<int>这是不合法的,要存储基本类型则可以利用基本类型的包装类如List<Integer> .List&l

Java泛型解析(02):通配符限定

Java泛型解析(02):通配符限定 考虑一个这样的场景,计算数组中的最大元素. [code01] public class ArrayUtil { public static <T> T max(T[] array) { if (array == null || 0 == array.length) { return null ;} T max = array[0]; for (int i = 1; i < array.length; i++) { if (max.compareTo(

Java泛型解析(01):认识泛型

What Java从1.0版本到现在的8,中间Java5中发生了一个很重要的变化,那就是泛型机制的引入.Java5引入了泛型,主要还是为了满足在1999年指定的最早Java规范之一.经过了5年左右的时间,专家组定义了一套泛型规范,实现后通过测试投入到使用.所以说泛型是Java5以后才有的,欲知详情,继续往下看. Why      换个角度想,Java5引入泛型,必定是它能带来好处,否则牛气的Java专家工程师就要遭到吐槽了.我们来吐槽一下没有泛型的程序是怎么写的. [code01] ArrayL

js上传文件带参数,并且,返回给前台文件路径,解析上传的xml文件,存储到数据库中

ajaxfileupload.js jQuery.extend({ createUploadIframe: function(id, uri) { //create frame var frameId = 'jUploadFrame' + id; if(window.ActiveXObject) { var io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '&qu

使用GSON和泛型解析约定格式的JSON串(转)

时间紧张,先记一笔,后续优化与完善. 解决的问题: 使用GSON和泛型解析约定格式的JSON串. 背景介绍: 1.使用GSON来进行JSON串与java代码的互相转换. 2.JSON的格式如下三种: 写道 #第一种: {"success":true,"data":{"averageStarLevel":4.7,"remarkCount":10}} #第二种: {"success":true,"da

Java泛型解析(03):虚拟机执行泛型代码

Java泛型解析(03):虚拟机执行泛型代码 Java虚拟机是不存在泛型类型对象的,所有的对象都属于普通类,甚至在泛型实现的早起版本中,可以将使用泛型的程序编译为在1.0虚拟机上能够运行的class文件,这个向后兼容性后期被抛弃了,所以后来如果用Sun公司的编译器编译的泛型代码,是不能运行在Java5.0之前的虚拟机的,这样就导致了一些实际生产的问题,如一些遗留代码如何跟新的系统进行衔接,要弄明白这个问题,需要先了解一下虚拟机是怎么执行泛型代码的. 虚拟机的一种机制:擦除类型参数,并将其替换成特

Android之基于Gson的ParameterizedType进行泛型解析

创建GsonResponsePasare解析类, class GsonResponsePasare<T> { T deal(String response) { Type gsonType = new TypeToken<CommonResponse<T>>() { }.getType(); CommonResponse<T> commonResponse = new Gson().fromJson(response, gsonType); lg.e(&qu

44个 Javascript 变态题解析 (上\下)

第1题 ["1", "2", "3"].map(parseInt) 知识点: Array/map Number/parseInt JavaScript parseInt 首先, map接受两个参数, 一个回调函数 callback, 一个回调函数的this值 其中回调函数接受三个参数 currentValue, index, arrary; 而题目中, map只传入了回调函数--parseInt. 其次, parseInt 只接受两个两个参数 s

44个 Javascript 变态题解析 (上)

原题来自: javascript-puzzlers(http://javascript-puzzlers.herokuapp.com/) 读者可以先去做一下感受感受. 当初笔者的成绩是 21/44- 当初笔者做这套题的时候不仅怀疑智商, 连人生都开始怀疑了-. 不过, 对于基础知识的理解是深入编程的前提. 让我们一起来看看这些变态题到底变态不变态吧! 第1题 ["1", "2", "3"].map(parseInt) 知识点: Array/map