简介
泛型是 C# 2.0 的最强大的功能。通过泛型可以定义类型安全的数据结构,而无须使用实际的数据类型。这能够显著提高性能并得到更高质量的代码,因为您可以重用数据处理算法,而无须复制类型特定的代码。在概念上,泛型类似于 C++ 模板,但是在实现和功能方面存在明显差异。本文讨论泛型处理的问题空间、它们的实现方式、该编程模型的好处,以及独特的创新(例如,约束、一般方法和委托以及一般继承)。
与 C++ 模板相比,C# 泛型可以提供增强的安全性,但是在功能方面也受到某种程度的限制。在一些 C++ 编译器中,在您通过特定类型使用模板类之前,编译器甚至不会编译模板代码。当您确实指定了类型时,编译器会以内联方式插入代码,并且将每个出现一般类型参数的地方替换为指定的类型。此外,每当您使用特定类型时,编译器都会插入特定于该类型的代码,而不管您是否已经在应用程序中的其他某个位置为模板类指定了该类型。C++ 链接器负责解决该问题,并且并不总是有效。这可能会导致代码膨胀,从而增加加载时间和内存足迹。
在 .NET 2.0 中,泛型在 IL(中间语言)和 CLR 本身中具有本机支持。在编译一般 C# 服务器端代码时,编译器会将其编译为 IL,就像其他任何类型一样。但是,IL 只包含实际特定类型的参数或占位符。此外,一般服务器的元数据包含一般信息。NET 中的泛型使您可以重用代码以及在实现它时付出的努力。类型和内部数据可以在不导致代码膨胀的情况下更改,而不管您使用的是值类型还是引用类型。您可以一次性地开发、测试和部署代码,通过任何类型(包括将来的类型)来重用它,并且全部具有编译器支持和类型安全。因为一般代码不会强行对值类型进行装箱和取消装箱,或者对引用类型进行向下强制类型转换,所以性能得到显著提高。对于值类型,性能通常会提高 200%;对于引用类型,在访问该类型时,可以预期性能最多提高 100%(当然,整个应用程序的性能可能会提高,也可能不会提高)。本文随附的源代码包含一个微型基准应用程序,它在紧密循环中执行堆栈。该应用程序使您可以在基于 Object 的堆栈和一般堆栈上试验值类型和引用类型,以及更改循环迭代的次数以查看泛型对性能产生的影响。
在项目中使用泛型的意义:
在实际项目中,我们似乎永远两条数据流,一条是从数据库查询得到,通过逻辑类,传向页面的数据集,我们通常用.net中提供的DataSet,DataTable的作为数据的“载体”,传向页面的数据一般很少做逻辑处理,应为我们的SQL语句早将数据逻辑处理了,我们得到的数据集既我们想要的。还有条数据既从页面流向我们逻辑处理类,在处理后流向数据库的数据链。我们可以选择DataTable,数组(字符串数组,ArrayList)来将这些数据装箱。
在选择DataTable作为数据载体时,我们通常要用这样的代码将我们所要的数据从DataTable从Unpack出来,(Type)dtbDataSource.Rows[0]["ItemNo"]。我之所用Unpack这个词,因为这个过程真的不亚于我们CRM流程中的Unpack操作。曾经用DataSet作为数据文件保存的单位(DataSet.ReadXml();DataSet.WriteXml()),为写出健壮的代码,你需要去check读出的DataSet中DataTable的columns的name,type;你如果要去读数据,对不起,那就和无休无止的(type)类型强制转化打上交道了,一不小心就会抛个错出来,毕竟这是个比较危险的操作。
还有一点,DataTable只能保存Boolean ,Byte, Char, DateTime, Decimal, Double, Int16, Int32, Int64, SByte, Single, String, TimeSpan, UInt16, UInt32, UInt64,相比之下,ArrayList 支持用户自定义数据类型的装箱,ArrayList.Add()方法可以将一个object添加到ArrayList中。虽然支持了对象的装箱和拆箱,但ArrayList中的元素依然不是强类型的,当你要引用ArrayList中的数据时,又回到了我们不断强制转换的老路:
ArrayList testList = new ArrayList();
myStruct newStruct = new myStruct("a",1);
testList.Add(newStruct);
myStruct testStruct = (myStruct)testList[0];
string a = testStruct.a;
int b = testStruct.b;
如果你写的代码不清楚在调用时会传什么类型的参数,那就无法直接从ArrayList中将所需要的数据类型拆箱出来。因而根本的问题还是没有解决。
要解决这个问题,那就要请出.NET 2.0中提供的泛型类了。.NET 2.0提供的泛型类在System.Collections.Generic命名空间下,共有三十多个泛型类、接口、结构。其中有数组类型的List<T>,堆栈类型的Stack<T>,支持键值排序的SortedDictionary<TKey,TValue>,先进先出队列类型的Queue<T>,双向链表类型的LinkedList<T>,类哈希表类型的Dictionary<TKey,Tvalue>(T可以是基本数据类型,也可以是自定义类型)。这些类都提供了基本数据结构操作,因而如果用复杂数据结构来完成复杂算法,这些泛型类是不错的选择。
不过如果只是项目中传递表结构的数据之用,那作为ArrayList的泛型等效类的List<>就足够满足需要了。
List<myStruct> testList = new List<myStruct>();
myStruct newStruct = new myStruct("a", 1);
testList.Add(newStruct);
myStruct testStruct = testList[0];
string a = testStruct.a;
int b = testStruct.b;
通过上面这段代码可以看到,泛型类给程序员最大的效能就是不要再对链表结构中的数据类型进行检测或强制转化,并能通过VS 2005开发环境的智能感知,知道链表中的数据结构。因为链表中的数据是强类型的,可以轻松的通过string a = testStruct.a;这样的形式将数据拆箱出来。
在项目的开发中我们就使用List<Structure> 作为CodeBehind向逻辑层传递数据的形式。当然使用泛型也需要一些另外的开销。比如我们需要为传递的数据构造结构体,以后的扩展可能需要涉及结构体的重新设计(当然这个代价应该比其他类型方式更小) 。但总体来说,当页面向逻辑层传递栏位不是很多的情况下,泛型是不错的选择。
就在为List<T>带来的方便欢欣鼓舞之时,我发现一个问题,如果要在某个List<T>中查找某个符合特定条件的T,用List<T>的F打头的方法:
public bool Exists(Predicate<T> match);
public T Find(Predicate<T> match);
public List<T> FindAll(Predicate<T> match);
public int FindIndex(Predicate<T> match);
public int FindIndex(int startIndex, Predicate<T> match);
public int FindIndex(int startIndex, int count, Predicate<T> match);
public T FindLast(Predicate<T> match);
public int FindLastIndex(Predicate<T> match);
public int FindLastIndex(int startIndex, Predicate<T> match);
public int FindLastIndex(int startIndex, int count, Predicate<T> match);
public void ForEach(Action<T> action);
原来Predicate<T>就是一个委托,看下Predicate<T>的定义
public delegate bool Predicate<T>(T obj);
给predicate指定一个参数类型为T,返回值类型为bool的“谓词”函数,F打头的函数就能依靠委托,从List<T>中返回出所需要的数据。
我们来写个谓词函数(参数类型为T,返回值类型为bool)
public bool check(myStruct match)
{
return match.a == "a";
}
然后把它指定给我们的谓词Predicate<T>
System.Predicate<myStruct> checkfirst = new System.Predicate<myStruct>(this.check);
checkfirst(testStruct);
testList.Find(checkfirst);
最后使用F打头函数,来实现我们希望的查找。