NET泛型编程已经离我们不远了,在微软最近随SQL Server Yukon Beta1发行的.NET Framework 1.2中就已经有了泛型的影子。虽然现在它还是问题多多,但是相信随着新版.NET Framework的正式发行,这些问题会得到解决。因此我们也该为.NET泛型编程做些准备了。
.NET系统是一个单根继承系统,所有的类型都派生自Object。我以前一直认为在单根继承系统中用不着泛型。既然所有的东西都可以作为Object传递,又何必使用泛型呢?只是增加复杂度而已,除了看起来高深一点,似乎没有别的什么好处了。但是,当两个最著名的单根系统,Java和.NET,都势不可挡地要加入泛型编程时,我不免要重新审视这个问题——为什么一定要泛型编程?
归纳起来,泛型比非泛型具有下面两个优点:
1、 更加安全
在非泛型编程中,虽然所有的东西都可以作为Object传递,但是在传递的过程中免不了要进行类型转换。而类型转换在运行时是不安全的。使用泛型编程将可以减少不必要的类型转换,从而提高安全性。
2、 效率更高
在非泛型编程中,将简单类型作为Object传递时会引起装箱和拆箱的操作,这两个过程都是具有很大开销的。使用泛型编程就不必进行装箱和拆箱操作了。
.NET泛型具有很好的二进制重用性。这一点得益于.NET将泛型内建在CLR之中。C++泛型和评估中Java泛型所依靠的是它们各自的编译器所提供的特性,编译器在编译泛型代码时将确切的类型展开,这就难免会出现代码膨胀的问题。而.NET的泛型代码是在运行时由JIT即时编译的,这样CLR就可以为不同类型重用大部分的即时编译代码了。
新C#将会支持泛型(Generics)、迭代器(Iterator)等泛型编程的特性。泛型可以使程序员更多的关注不同类型的共通算法的设计,由此大大提高开发程序的速度。
泛型的使用原则和实现原理是将操作类型参数化,用泛化的参数来传递参数类型的信息。这样,象List等通用算法就可以简单的写出一个泛型版本,在编译器编译的时候根据泛化的类型不同而生成不同的类,不会占用运行期时间,这样即节省了开发时间,也节省了运行时间。
说了半天我们来看看一个泛型的例子,大家对它就会有一个真切的了解了。
public class Stack
{
object[] m_Items;
public void Push(object item) {...}
public object Pop() {...}
}
这是一个没有使用泛型的堆栈类代码。下面看看我们一般怎么使用它。
Stack stack = new Stack();
stack.Push("1");
string number = (string)stack.Pop();
Stack stack = new Stack(); stack.Push(1);
string number = (string)stack.Pop();
这段代码在使用的过程中,简单类型将会被装箱,这样才可能将它推入堆栈。而在弹出的过程中,简单类型的数据又需要拆箱过程来取回值。这样消耗了大量的计算时间。拆箱后的数据类型是一个强制转换的过程,可能会造成数据类型的错误,而留下隐患。
public class Stack
{
T[] m_Items;
public void Push(T item) {...}
public T Pop() {...}
}
Stack stack = new Stack(); stack.Push(1);
stack.Push(2);
int number = stack.Pop();
这就是泛型的最一般的例子。当一个泛型类,被泛化的时候,如上面的Stack stack …出现的时候,编译器就会生成一个类。这个类的结构和Stack所描述的是一样的,特殊的是它使用int来代替T来生成一个类我们假定它叫做intStack,所有的stack对象的操作,都会直接按照它是一个intStack类型来处理。这样如果还有其他的泛化形式,也会相应的生成对应的类。如果泛化的类型是一个引用类型,那么编译器会把它转化成Object的Stack类,也就是说不会为每一个引用类型的泛型做一次泛化。
这样虽然最后生成的程序体积会有所增加,但是大大减少了编程时间和运行时间,也提高了程序的类型安全性。使用Stack等表示容器的泛型在编程时必然会碰到对容器中单个数据的操作问题。解决之道就是下面要介绍的迭代器。
C#中的foreach语句用于迭代一个可枚举(enumerable)的集合中的元素。为了实现可枚举,一个集合必须要有一个无参的、返回枚举器(enumerator)的GetEnumerator方法。通常,枚举器是很难实现的,因此简化枚举器的任务意义重大。
迭代器(iterator)是一块可以产生(yields)值的有序序列的语句块。迭代器通过出现的一个或多个yield语句来区别于一般的语句块:
• yield return语句产生本次迭代的下一个值。
• yield break语句指出本次迭代完成。
只要一个函数成员的返回值是一个枚举器接口(enumerator interfaces)或一个可枚举接口(enumerable interfaces),我们就可以使用迭代器:
• 所谓枚举器借口是指System.Collections.IEnumerator和从System.Collections.Generic.IEnumerator构造的类型。
• 所谓可枚举接口是指System.Collections.IEnumerable和从System.Collections.Generic.IEnumerable构造的类型。
理解迭代器并不是一种成员,而是实现一个功能成员是很重要的。一个通过迭代器实现的成员可以用一个或使用或不使用迭代器的成员覆盖或重写。
下面的Stack类使用迭代器实现了它的GetEnumerator方法。其中的迭代器按照从顶端到底端的顺序枚举了栈中的元素。
using System.Collections.Generic;
public class Stack: IEnumerable
{
T[] items;
int count;
public void Push(T data) {...}
public T Pop() {...}
public IEnumerator GetEnumerator() {
for (int i = count – 1; i >= 0; --i) {
yield return items[i];
}
}
}
GetEnumerator方法的出现使得Stack成为一个可枚举类型,这允许Stack的实例使用foreach语句。下面的例子将值0至9压入一个整数堆栈,然后使用foreach循环按照从顶端到底端的顺序显示每一个值。
using System;
class Test
{
static void Main() {
Stack stack = new Stack();
for (int i = 0; i < 10; i++) stack.Push(i);
foreach (int i in stack) Console.Write("{0} ", i);
Console.WriteLine();
}
}
这个例子的输出为:
9 8 7 6 5 4 3 2 1 0
语句隐式地调用了集合的无参的GetEnumerator方法来得到一个枚举器。一个集合类中只能定义一个这样的无参的GetEnumerator方法,不过通常可以通过很多途径来实现枚举,包括使用参数来控制枚举。在这些情况下,一个集合可以使用迭代器来实现能够返回可枚举接口的属性和方法。例如,Stack可以引入两个新的属性——IEnumerable类型的TopToBottom和BottomToTop:
using System.Collections.Generic;
public class Stack: IEnumerable
{
T[] items;
int count;
public void Push(T data) {...}
public T Pop() {...}
public IEnumerator GetEnumerator() {
for (int i = count – 1; i >= 0; --i) {
yield return items[i];
}
}
public IEnumerable TopToBottom {
get {
return this;
}
}
public IEnumerable BottomToTop {
get {
for (int i = 0; i < count; i++) {
yield return items[i];
}
}
}
}
TopToBottom属性的get访问器只返回this,因为堆栈本身就是一个可枚举类型。BottomToTop属性使用C#迭代器返回了一个可枚举接口。下面的例子显示了如何使用这两个属性来以任意顺序枚举栈中的元素:
using System;
class Test
{
static void Main() {
Stack stack = new Stack();
for (int i = 0; i < 10; i++) stack.Push(i);
foreach (int i in stack.TopToBottom) Console.Write("{0} ", i);
Console.WriteLine();
foreach (int i in stack.BottomToTop) Console.Write("{0} ", i);
Console.WriteLine();
}
}
当然,这些属性还可以用在foreach语句的外面。下面的例子将调用属性的结果传递给一个独立的Print方法。这个例子还展示了一个迭代器被用作一个带参的FromToBy方法的方法体:
using System;
using System.Collections.Generic;
class Test
{
static void Print(IEnumerable collection) {
foreach (int i in collection) Console.Write("{0} ", i);
Console.WriteLine();
}
static IEnumerable FromToBy(int from, int to, int by) {
for (int i = from; i <= to; i += by) {
yield return i;
}
}
static void Main() {
Stack stack = new Stack();
for (int i = 0; i < 10; i++) stack.Push(i);
Print(stack.TopToBottom);
Print(stack.BottomToTop);
Print(FromToBy(10, 20, 2));
}
}
这个例子的输出为:
9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 7 8 9
10 12 14 16 18 20
泛型和非泛型的可枚举接口都只有一个单独的成员,一个无参的GetEnumerator方法,它返回一个枚举器接口。一个可枚举接口很像一个枚举器工厂(enumerator factory)。每当调用了一个正确地实现了可枚举接口的类的GetEnumerator方法时,都会产生一个独立的枚举器。
using System;
using System.Collections.Generic;
class Test
{
static IEnumerable FromTo(int from, int to) {
while (from <= to) yield return from++;
}
static void Main() {
IEnumerable e = FromTo(1, 10);
foreach (int x in e) {
foreach (int y in e) {
Console.Write("{0,3} ", x * y);
}
Console.WriteLine();
}
}
}
上面的代码打印了一个从1到10的简单乘法表。注意FromTo方法只调用了一次用来产生可枚举接口e。而e.GetEnumerator()被调用了多次(通过foreach语句)来产生多个相同的枚举器。这些枚举器都封装了FromTo声明中指定的代码。注意,迭代其代码改变了from参数。不过,枚举器是独立的,因为对于from参数和to参数,每个枚举器拥有它自己的一份拷贝。在实现可枚举类和枚举器类时,枚举器之间的过渡状态(一个不稳定状态)是必须消除的众多细微瑕疵之一。C#中的迭代器的设计可以帮助消除这些问题,并且可以用一种简单的本能的方式来实现健壮的可枚举类和枚举器类。
理解和掌握泛型编程为我们更方便的写出稳定、安全的程序提供了便捷的方式。它将是未来C#发展的一个重要方面,会为C#乃至.Net的发展做出里程碑意义的贡献。
C#泛型集合
2007-12-15 23:59
来源:aspx51.com 作者:网络 点击次数: 0 创建时间:2007-11-28 字体:大 中 小
集合是OOP中的一个重要概念,C#中对集合的全面支持更是该语言的精华之一。
为什么要用泛型集合?
在C# 2.0之前,主要可以通过两种方式实现集合:
a.使用ArrayList
直接将对象放入ArrayList,操作直观,但由于集合中的项是Object类型,因此每次使用都必须进行繁琐的类型转换。
b.使用自定义集合类
比较常见的做法是从CollectionBase抽象类继承一个自定义类,通过对IList对象进行封装实现强类型集合。这种方式要求为每种集合类型写一个相应的自定义类,工作量较大。泛型集合的出现较好的解决了上述问题,只需一行代码便能创建指定类型的集合。
什么是泛型?
泛型是C# 2.0中的新增元素(C++中称为模板),主要用于解决一系列类似的问题。这种机制允许将类名作为参数传递给泛型类型,并生成相应的对象。将泛型(包括类、接口、方法、委托等)看作模板可能更好理解,模板中的变体部分将被作为参数传进来的类名称所代替,从而得到一个新的类型定义。泛型是一个比较大的话题,在此不作详细解析,有兴趣者可以查阅相关资料。
怎样创建泛型集合?
主要利用System.Collections.Generic命名空间下面的List泛型类创建集合,语法如下:
List<T> ListOfT = new List<T>();
其中的"T"就是所要使用的类型,既可以是简单类型,如string、int,也可以是用户自定义类型。下面看一个具体例子。
定义Person类如下:
class Person
{
private string _name; //姓名
private int _age; //年龄
//创建Person对象
public Person(string Name, int Age)
{
this._name= Name;
this._age = Age;
}
//姓名
public string Name
{
get { return _name; }
}
//年龄
public int Age
{
get { return _age; }
}
}
//创建Person对象
Person p1 = new Person("张三", 30);
Person p2 = new Person("李四", 20);
Person p3 = new Person("王五", 50);
//创建类型为Person的对象集合
List<Person> persons = new List<Person>();
//将Person对象放入集合
persons.Add(p1);
persons.Add(p2);
persons.Add(p3);
//输出第2个人的姓名
Console.Write(persons[1].Name);
可以看到,泛型集合大大简化了集合的实现代码,通过它,可以轻松创建指定类型的集合。非但如此,泛型集合还提供了更加强大的功能,下面看看其中的排序及搜索。
泛型集合的排序
排序基于比较,要排序,首先要比较。比如有两个数1、2,要对他们排序,首先就要比较这两个数,根据比较结果来排序。如果要比较的是对象,情况就要复杂一点,比如对Person对象进行比较,则既可以按姓名进行比较,也可以按年龄进行比较,这就需要确定比较规则。一个对象可以有多个比较规则,但只能有一个默认规则,默认规则放在定义该对象的类中。默认比较规则在CompareTo方法中定义,该方法属于IComparable<T>泛型接口。请看下面的代码:
class Person :IComparable<Person>
{
//按年龄比较
public int CompareTo(Person p)
{
return this.Age - p.Age;
}
}
CompareTo方法的参数为要与之进行比较的另一个同类型对象,返回值为int类型,如果返回值大于0,表示第一个对象大于第二个对象,如果返回值小于0,表示第一个对象小于第二个对象,如果返回0,则两个对象相等。
定义好默认比较规则后,就可以通过不带参数的Sort方法对集合进行排序,如下所示:
//按照默认规则对集合进行排序
persons.Sort();
//输出所有人姓名
foreach (Person p in persons)
{
Console.WriteLine(p.Name); //输出次序为"李四"、"张三"、"王五"
}
实际使用中,经常需要对集合按照多种不同规则进行排序,这就需要定义其他比较规则,可以在Compare方法中定义,该方法属于IComparer<T>泛型接口,请看下面的代码:
class NameComparer : IComparer<Person>
{
//存放排序器实例
public static NameComparer Default = new NameComparer();
//按姓名比较
public int Compare(Person p1, Person p2)
{
return System.Collections.Comparer.Default.Compare(p1.Name, p2.Name);
}
}
Compare方法的参数为要进行比较的两个同类型对象,返回值为int类型,返回值处理规则与CompareTo方法相同。其中的Comparer.Default返回一个内置的Comparer对象,用于比较两个同类型对象。
下面用新定义的这个比较器对集合进行排序:
//按照姓名对集合进行排序
persons.Sort(NameComparer.Default);
//输出所有人姓名
foreach (Person p in persons)
{
Console.WriteLine(p.Name); //输出次序为"李四"、"王五"、"张三"
}
还可以通过委托来进行集合排序,首先要定义一个供委托调用的方法,用于存放比较规则,可以用静态方法。请看下面的代码:
class PersonComparison
{
//按姓名比较
public static int Name(Person p1, Person p2)
{
return System.Collections.Comparer.Default.Compare(p1.Name, p2.Name);
}
}
方法的参数为要进行比较的两个同类型对象,返回值为int类型,返回值处理规则与CompareTo方法相同。然后通过内置的泛型委托System.Comparison对集合进行排序:
System.Comparison<Person> NameComparison = new System.Comparison<Person>(PersonComparison.Name);
persons.Sort(NameComparison);
//输出所有人姓名
foreach (Person p in persons)
{
Console.WriteLine(p.Name); //输出次序为"李四"、"王五"、"张三"
}
可以看到,后两种方式都可以对集合按照指定规则进行排序,但笔者更偏向于使用委托方式,可以考虑把各种比较规则放在一个类中,然后进行灵活调用。
泛型集合的搜索
搜索就是从集合中找出满足特定条件的项,可以定义多个搜索条件,并根据需要进行调用。首先,定义搜索条件,如下所示:
class PersonPredicate
{
//找出中年人(40岁以上)
public static bool MidAge(Person p)
{
if (p.Age >= 40)
return true;
else
return false;
}
}
上面的搜索条件放在一个静态方法中,方法的返回类型为布尔型,集合中满足特定条件的项返回true,否则返回false。然后通过内置的泛型委托System.Predicate<T>对集合进行搜索:
System.Predicate<Person> MidAgePredicate = new System.Predicate<Person>(PersonPredicate.MidAge);
List<Person> MidAgePersons = persons.FindAll(MidAgePredicate);
//输出所有的中年人姓名
foreach (Person p in MidAgePersons)
{
Console.WriteLine(p.Name); //输出"王五"
}
泛型集合的扩展
如果要得到集合中所有人的姓名,中间以逗号隔开,那该怎么处理?
考虑到单个类可以提供的功能是有限的,很自然会想到对List<T>类进行扩展,泛型类也是类,因此可以通过继承来进行扩展。请看下面的代码:
//定义Persons集合类
class Persons : List<Person>
{
//取得集合中所有人姓名
public string GetAllNames()
{
if (this.Count == 0)
return "";
string val = "";
foreach (Person p in this)
{
val += p.Name + ",";
}
return val.Substring(0, val.Length - 1);
}
}
//创建并填充Persons集合
Persons PersonCol = new Persons();
PersonCol.Add(p1);
PersonCol.Add(p2);
PersonCol.Add(p3);
//输出所有人姓名
Console.Write(PersonCol.GetAllNames()); //输出“张三,李四,王五”
小结:
本文着重于介绍运用C# 2.0中的泛型来实现集合,以及对集合功能进行扩展,恰当的运用泛型集合,可以减少很多重复工作,极大的提高开发效率。实际上,集合只不过是泛型的一个典型应用,如果想了解更多关于泛型的知识,可以查阅其他相关资料。希望本文对你有用:-)
C# 泛型
Posted on 2007-08-15 15:55 星际 阅读(311) 评论(0) 编辑 收藏 所属分类: 网摘 、学习心得
首先我们来看一下泛型的基本概念。
最显著的一点就是它参数化了类型,把类型作为参数抽象出来,从而使我们在实际的运用当中能够更好的实现代码的重复利用,同时它提供了更强的类型安全,更高的效率,不过在约束方面,它只支持显示的约束,这样在灵活性方面就显得不是那么好了。我觉得它之所以能够提供更高的效率是因为泛型在实例化的时候采用了"on-demand"的模式,即按需实例化,发生在JIT(Just In Time)编译时。
下面来看如何定义一个泛型类,很简单,你只需要意识到一点,在这里,类型已经被参数化了:
using System;
using System.Collections.Generic;
using System.Text;
namespace GenericTest
{
class Program
{
static void Main(string[] args)
{
//使用string,int来实例化Test<T,S>类
Test<string, int> t = new Test<string, int>("SHY520",22);
//调用泛型类中的方法
t.SetValue();
}
}
/** <summary>
/// 定义一个泛型类,该类有两个类型参数,分别是T,S
/// http://pw.cnblogs.com
/// </summary>
/// <typeparam name="T">类型参数</typeparam>
/// <typeparam name="S">类型参数</typeparam>
public class Test<T,S>
{
//泛型类的类型参数可用于类成员
private T name;
private S age;
public Test(T Name,S Age)
{
this.name = Name;
this.age = Age;
}
public void SetValue()
{
Console.WriteLine(name.ToString());
Console.WriteLine(age.ToString());
}
}
}
上面的例子不是很恰当,目的是让初学泛型的你了解一下泛型的定义及实例化方法,如上,我们定义了一个泛型类,那么如何实现泛型类的继承呢?这里需要满足下面两点中的任何一点即可:
1、泛型类继承中,父类的类型参数已被实例化,这种情况下子类不一定必须是泛型类;
2、父类的类型参数没有被实例化,但来源于子类,也就是说父类和子类都是泛型类,并且二者有相同的类型参数;
//如果这样写的话,显然会报找不到类型T,S的错误
public class TestChild : Test<T, S> { }
//正确的写法应该是
public class TestChild : Test<string, int>{ }
public class TestChild<T, S> : Test<T, S> { }
public class TestChild<T, S> : Test<String, int> { }
接着我们来看看泛型接口,其创建以及继承规则和上面说的泛型类是一样的,看下面的代码:
public interface IList<T>
{
T[] GetElements();
}
public interface IDictionary<K,V>
{
void Add(K key, V value);
}
// 泛型接口的类型参数要么已实例化
// 要么来源于实现类声明的类型参数
class List<T> : IList<T>, IDictionary<int, T>
{
public T[] GetElements() { return null; }
public void Add(int index, T value)
{}
}
在来看一下泛型委托,首先我们定义一个类型参数为T的委托,然后在类中利用委托调用方法:
using System;
using System.Collections.Generic;
using System.Text;
namespace GenericTest
{
//定义一个委托,类型参数为T,返回值类型T
//泛型委托支持在返回值和参数上应用类型参数
delegate string GenericDelete<T>(T value);
class test
{
static string F(int i) { return "SHY520"; }
static string G(string s) { return "SHY520"; }
static void Main(string[] args)
{
GenericDelete<string> G1 = G;
GenericDelete<int> G2 = new GenericDelete<int>(F);
}
}
}
我们再来看泛型方法,C#的泛型机制只支持在方法申明上包含类型参数,也即是泛型方法。特别注意的是,泛型不支持在除了方法以外的其他类/接口成员上使用类型参数,但这些成员可以被包含在泛型类型中,并且可以使用泛型类型的类型参数。还有一点需要说的就是,泛型方法可以在泛型类型中,也可以存在于非泛型类型中。下面我们分别看一下泛型类型的申明,调用,重载和覆盖。
using System;
using System.Collections.Generic;
using System.Text;
namespace GenericTest
{
class GenericClass
{
//申明一个泛型方法
public T getvalue<T>(T t)
{
return t;
}
//调用泛型方法
//注意:在调用泛型方法时,对泛型方法的类型参数实例化
public int useMethod()
{
return this.getvalue<int>(10);
}
//重载getvalue方法
public int getvalue(int i)
{
return i;
}
}
//下面演示覆盖
//要注意的是,泛型方法被覆盖时,约束被默认继承,不需要重新指定约束关系
abstract class Parent
{
public abstract K TEST<K, V>(K k, V v) where K : V;
}
class Child : Parent
{
public override T TEST<T, S>(T t, S s)
{
return t;
}
}
}
最后我们来看一下泛型中的约束:
C#中的泛型只支持显示的约束,因为这样才能保证C#所要求的类型安全,但显示的约束并非时必须的,如果不加约束,泛型类型参数将只能访问System.Object类型中的公有方法。“显式约束”由where子句表达,可以指定“基类约束”,“接口约束”,“构造器约束”,“值类型/引用类型约束”共四种约束。下面的例子来源于李建忠老师的讲座PPT。
1、基类约束:
class A { public void F1() {} }
class B { public void F2() {} }
class C<S,T>
where S: A // S继承自A
where T: B // T继承自B
{
// 可以在类型为S的变量上调用F1,
// 可以在类型为T的变量上调用F2
}
2、接口约束
interface IPrintable { void Print(); }
interface IComparable<T> { int CompareTo(T v);}
interface IKeyProvider<T> { T GetKey(); }
class Dictionary<K,V>
where K: IComparable<K>
where V: IPrintable, IKeyProvider<K>
{
// 可以在类型为K的变量上调用CompareTo,
// 可以在类型为V的变量上调用Print和GetKey
}
3、构造器约束
class A { public A() { } }
class B { public B(int i) { } }
class C<T>
where T : new()
{
//可以在其中使用T t=new T();
}
C<A> c=new C<A>(); //可以,A有无参构造器
C<B> c=new C<B>(); //错误,B没有无参构造器
4、值/引用类型约束
public struct A { }
public class B { }
class C<T>
where T : struct
{
// T在这里面是一个值类型
}
C<A> c=new C<A>(); //可以,A是一个值类型
C<B> c=new C<B>(); //错误,B是一个引用类型
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/jingshuaizh/archive/2008/03/08/2158253.aspx