初学泛型
前言
前几日同学告诉我他老师告诉他,泛型是定义一种通用的方法,至于为什么用泛型他的答案还是定义一种通用的方法,当时我也没有学,还不懂啦,毕竟人家是计算机专业的科班生,我当然没有发言权,只是怀疑的记下了这个错误的结论打算以后看看是不是对的,这两天算是把泛型学了一下,其实它定义的不只是方法,自己感觉他就是一种规范代码执行效率的模版,这样极不容易出错,并把我们常用的箱拆箱操作过程给省了,提高了编译器的执行效率,。至于为什么用泛型这个我貌似已经说明白了,泛型多用于集合类操作,我在这里随意的拿出C#定义的ArrayList的 Add方法的定义我们一起看看:
publicvirtual int Add (
Object value
)
可以看出我们可以往ArrayList里添加任何类型的元素,假如有一个string类型的str字符串它在添加到ArrayList lib的过程中实际进行了装箱操作,即object value=str;那么我们在获取这个值得时候必须这样使用string s=(string) lib[0];不能遗漏(string)否则会提示你,
错误 1 无法将类型“object”隐式转换为“string”。存在一个显式转换(是否缺少强制转换?)
但是你假如这样写:int a =(int) lib[0];程序在运行前是无法报错的,只要一旦运行就会出现异常,而这些错误都可以利用泛型轻松搞定,让你在编译时就可以发现错误,具体的就看你怎么领悟了,我在文章里没有写任何定义的语法格式,因为这个既没必要说,这是常识,出来混这些迟早是要明白的,呵呵。玩笑啦......欢迎新手同学来我的博客交流学习,我也希望你们可以为我敞开心怀,因为我既不是计算机专业的学生,甚至上了大学我已经不再是理工类的学生。我无意扎进了文科专业去叱咤风云啦,哎,这个文科专业名字太有点科学技术的味道了。我喜欢 ........
关于泛型的几个重要概念:
泛型类型形式参数(类型形参):是泛型类型或泛型方法等定义中的占位符。类型形参一般以大写字母 T 开头,如果只有一个类型形参,
一般用 T 来表示。
构造泛型类型(构造类型):是为泛型类型定义的泛型类型参数指定类型得到的结果。
泛型类型实际参数(类型实参):是替换泛型形参的任何类型。
约束:是加在泛型类型参数上的限制。使用泛型类型的客户端不能替换不满足这些约束的类型参数。
泛型类:
泛型类型可以根据类型形参的数目进行“重载”,也就是两个类型声明可以使用相同的标识符,只要这两个声明具有不同数目的类型参数
即可。例如:
class A { };
class A<T> { };
class A<U, V> { };
此时如果我们在定义一个 class A<C>; 或 class A<A, B> { }; 就会报错,为什么呢,原因很简单,这就是我前面提到的这些大写字母本
身没有多大意义,只是一个(等待替换的未知实际参数的)占位符即类型参数。非泛型类类声明的中指定的基类或者基接口可以是构造类型但必须是封闭构造类型。(也就.NET 的 CLR 的 JIT 将泛型 IL 和元数据转换为本机代码,并在该过程中将类型形参替换为类型实参)但是不能是
类型形参,而在基类或者基接口的作用域中可以包含类型形参。下面我做下详细解释,例如:
usingSystem;
namespaceGeneric
{
class A<U, V>
{
public virtual void F1(U a, V b)
{
Console.WriteLine("F1中包含的参数的 CTS 类型为:\n{0}\n{1}",
a.GetType(), b.GetType());
}
}
interface I<T>
{
T F2(T x);
//基类或者基接口的作用域中可以包含类型形参
}
class ChildTest1:A<string, string>
{
//重写基类的 F1方法
public override void F1(string a, string b)
{
Console.WriteLine(a+b);
}
}
class ChidTest2:A<string ,int>,I<string>
{
//实现基接口中的方法
public string F2(string x)
{
return ("F2包含的参数值为:"+x);
}
//该类隐藏了基类的 F1方法
public new void F1(string a, int b)
{
Console.WriteLine("ChidTest2类隐藏了基类的 F1方法");
}
}
//测试类
class Test
{
static void Main(string[] args)
{
//泛型类 A 只有初始化类型参数后才有意义
A<string, int> a = new A<string, int>();//注意:其中 A<string,
int>为构造类型,也就是初始化类型形参
ChildTest1 cd1 = new ChildTest1();
ChidTest2 cd2 = new ChidTest2();
a.F1("123",123);
cd1.F1("ChildTest1类重写了基类的","F1方法");
cd2.F1("123",123);
Console.WriteLine(cd2.F2("123"));
Console.ReadKey(true);
}
}
}
在上面的代码中我只是介绍了非泛型类的继承,那么泛型类的继承是不是一样呢?这个大家可以先自己想一想,我在讲到泛型接口的时候
会提到,下面继续说一下啊泛型类的成员的特点。泛型类中所有的成员都可以直接作为构造类型或者构造类型的一部分来使用包容类enclosed class)中的类型形参。当公共语言运行时使用特定的封闭构造类型时,所出现的每个类型形参都被替换该构造类型提供的类型实参。例如:
usingSystem;
namespaceGeneric
{
class A<T>
{
//定义两个类型字段
public T a; //作为构造类型的一部分(成员)使用类型形参 T
public A<T> b = null;//直接作为构造类型使用类型形参 T
//实例构造函数,其中 this 是 A<T>的实例
public A(T x)
{
this.a = x;
this.b = this;
}
}
//测试类
class Test
{
static void Main(string[] args)
{
A<string> A1 = new A<string>("Hello World");
Console.WriteLine(A1.a);
Console.WriteLine(A1.b.a);
A<int> A2 = new A<int>(123);
Console.WriteLine(A1.a);
Console.WriteLine(A1.b.a);
Console.ReadKey(true);
}
}
}
可能这里有的同学不理解我说的封闭构造类型,我前面提到了构造类型,我在这里在解释一下,例如A<T>和A<string>都是构造类型,我们将(使用一个或多个类型形参的构造类型)A<T>称为开放构造类型;将(不实用类型形参(即使用实参)的构造类型)A<string>称为封闭构造类型。此外对于上面的代码我可以这样解释:即构造类型成员可以直接或一部分作为开放构造类型使用类型形参,可以直接或一部分作为
封闭构造类型使用类型实参。
泛型接口
我先前提到过类型形参只是一个(等待替换的未知实际参数的)占位符。那么如果这样接口interface I<T>,interface I<A> , interface I<C>其实是一样的,同时和泛型类一样,可以根据类型形参的数目进行“重载”,也就是两个类型声明可以使用相同的标识符,只要这两个声明
具有不同数目的类型参数。那么我们这样想,假如我要用一个类实现interfaceI<string>和
I <string> 和
interface I <int>
interface I<T>
{
void F();
}
,我们是否可以使用形如:
class A<U, V> : I<U>, I<V> //之后替换为interface I <string> 和interface I <int>
{
void I<U>.F()
{
}
void I<V>.F()
{
}
}
这样编译器便会提示:
错误 1
“Generic.A<U,V>”不能同时实现“Generic.I<U>”和“Generic.I<V>”,原因是它们对于某些类型形参替换可以进行统一
也就是说,当使用泛型类实现泛型接口必须试所有可能的泛型接口的构造类型(例如I<U>)保持唯一,否则编译器无法确定该为某个泛型接口的显示成员调用那个方法。 可能以为I<U>就是I<V>)。但是我们可以将不同继承级别指定的接口进行统一。例如:
interface I<T>
{
void F();
}
class Base<U> : I<U>
{
void I<U>.F()
{
Console.WriteLine("I<U>.F()");
}
}
class Derived<U, V> : Base<U>, I<V>
{
void I<V>.F()
{
Console.WriteLine("I<U>.F()");
}
}
class Test
{
public static void Main()
{
I<int> test1 =new Base<int>();
I<int> test2= new Derived<string, int>();
test1.F();
test2.F();
Console.ReadKey();
Console.ReadKey();
}
}
从上面的代码可以看出,虽然Derived<U,V> 同时实现了I<U>和I<V> , test2.F() 这 里 将 调 用 I<V>() 的 方 法 , 实 际 上 实 在Derived<U,V>中重新实现了I<int>.这里我们再回到上面我说泛型类的时候,泛型类继承的特点,这里一看代码便已明了,泛型类继承的基类或者基接口可以是开放的构造类型也可以使封闭的构造类型(我已测试,这个大家也可以测试一下、)。
泛型方法
泛型方法可以在类,结构,接口声明中声明,这些类,结构,接口本身可以是泛型或者非泛型。
1.如果泛型方法在泛型类型中声明,则方法体可以同时引用该方法的类型形参和包含该方法的声明(泛型类,结构,接口)的
类型形参。
2.类型形参可以作为返回类型或形参的类型。
3.方法的类型形参的名称不能与相同方法中的普通形参的名称相同。
下面我对上面的问题一一解释:
class A<T>
{
public static void Print<U, V>(U a, V b, T c)
{
Console.WriteLine("{0}\n{1}\n{2}", a.GetType(), b.GetType(), c.GetType());
}
}
class Test
{
public static void Main()
{
A<int>.Print<string, decimal>("str", 125.25m, 15);
Console.ReadKey();
}
}
对应问题 1
class A
{
public static U Print<U, V>(U a, V b)
{
return a;
}
}
class Test
{
public static void Main()
{
Console.WriteLine( A.Print<string, byte>("str", 125));
Console.ReadKey();
}
}
对应问题2
public static U Print<U, V>(U U, V b)假如我们这里设置如此,语法没有错误,但是编译器运行
时会提示:
错误 1
“U”: 参数或局部变量不能与方法类型形参同名
对应问题3
此外泛型方法除了非泛型方法的签名(由方法的名称和他的每一个形参(按从左到右的顺序)的类型和种类(值,引用,输出)组成。)要素外,泛型方法还包括泛型类型形参的数目和类型形参的序号位置按从左到右),记住前面说过的类型形参只是个占位符。例如:
T F<T>(T a, int b)
{
return a;
}
void F (){ }
string F(string a,int b){};
}
上面的代码由于方法名相同,但是不会报错,为什么呢,这就涉及到方法的重载策略,但是假如出现:
public void F<U>(U a, int b) { },那么就会报错,因为这两个泛型类型形参的数目和类型形参的序号位置都是一样的。假如我在这样改一下:
public void F<U>(int b,U a )
重载策略的衍生,其实是一样的原理。
泛型委托
这里普通的泛型委托和普通委托一样很简单,我来试着测试一下匿名方法是不是也一样简单好了。
public delegate T Degle<T>(int a, T b);
class Test
{
public static void Main()
{
Degle<string> dg = delegate(int a, string b)
{
return b;
};
Console.WriteLine(dg(125, "使用了匿名方法创建泛型委托"));
Console.ReadKey();
}
}
约束
在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类进行限制,如果客户端代码使用某个约束不允许的类型来实例化类,就会产生编译时错误。这种限制称为约束。 约束是使用
where 上下文关键字指定的。下表列出了六种类型的约束:
约束 |
说明 |
T:结构 |
类型参数必须是值类型。可以指定除 Nullable 以外的任何值 型。有关更多信息,请参见使用可空类型((C#C# 编程指南)。 |
T:类 |
类型参数必须是引用类型,包括任何类、接口、委托或数组型。 |
T:new() |
类型参数必须具有无参数的公共构造函数 。当与其他约束一起用 用时,,new()new() 约束必须最后指定。 |
T:<基类名名>> |
类型参数必须是指定的基类或派生自指定的基类。 |
T:<接口名称称>> |
类型参数必须是指定的接口或实现指定的接口 。可以指定多个接 口约束。约束接口也可以是泛型的。 |
T:U |
为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 供的参数。这称为裸类型约束。 |
如果要检泛型列表中的某个项以确定它是否有效,或者将它与其他某个项进行比较,则编译器必须在一定程度上保证它需要调用的运算符或方法将受到客户端代码可能指定的任何类型参数的支持。这种保证是通过对泛型类定义应用一个或多个约束获得的。例如,基类约束告诉编译器:仅此类型的对象或从此类型派生的对象才可用作类型参数。一旦编译器有了这个保证,它就能够允许在泛型类中调用该类型的方法。首先我们先看一个(不使用约束的)例子:
using System;
using System.Collections;
namespace Generic
{
public class Student
{
private string name;
public string Name
{
set
{
name = value;
}
get
{
return name;
}
}
public Student(string s)
{
name = s;
}
}
public class Grade<T> //定义一个年级类
ArrayList names = new ArrayList(10);
public void Add(T t)
{
names.Add(t.Name);//这里便会出错
}
public void Display()
{
foreach (string s in names)
{
Console.WriteLine(s);
}
}
}
public class Test
{
public static void Main()
{
Grade<Student> gn = new Grade<Student>();
gn.Add(new Student("小明"));
gn.Add(new Student("小花"));
gn.Add(new Student("阿猫"));
gn.Add(new Student("阿狗"));
Console.WriteLine("一年级的同学有:");
gn.Display();
}
}
}
运行就会提示:
错误 1
“T”不包含“Name”的定义,并且找不到可接受类型为“T”的第一个参数的扩展方法“Name”(是否缺少 using 指令或程序集引用?)
也就是说,我们在使用 T 为实际形参占位的时候,编译器运行到names.Add(t.Name);
例到底有没有Name属性,或者这个Name到底是字段还是属性呢?程序是一步一步走的,他没有人的逻辑那样跳步完成某个推理,所以这里就会报错,因此我们就应该提前告知编译器这里类型形参T所能代表的具体范围,这样就可以解决问题了。因此这里就引出了约束来轻松的解决这一问题。我们只需要修改一句:public class Grade<T> where T:Student 我们在 Grade<T>的类中增加了约束,修改以后程序就可以便已通过,因为我们已经告知编译器 Grade<T>的 T 是 Student 类或者派生自 Student 类的,所以可以使用Stundent.Name 属性。
usingSystem;
usingSystem.Collections;
namespaceGeneric
{
public class Student
{
private string name;
public string Name
{
set
{
name = value;
}
get
{
return name;
}
}
public Student(string s)
{
name = s;
}
}
public class Grade<T> where T:Student
{
ArrayList names = new ArrayList(10);
public void Add(T t)
{
names.Add(t.Name);//这里便会出错
}
public void Display()
{
foreach (string s in names)
{
Console.WriteLine(s);
}
}
}
public class Test
{
public static void Main()
{
Grade<Student> gn = new Grade<Student>();
gn.Add(new Student("小明"));
gn.Add(new Student("小花"));
gn.Add(new Student("阿猫"));
gn.Add(new Student("阿狗"));
Console.WriteLine("一年级的同学包括");
gn.Display();
}
}
}
可以对同一类型的参数应用多个约束,并且约束自身可以是泛型类型,例如:
public class Grade<T> where T:Student,IStudent,System.IComparable<T>,new()
{
//...
}
这里重要一点的就是new(),new约束指定泛型类声明中的任何类型参数都必须有公共的无参数构造函数。如果要使用new约束,则该类型不能为抽象类型。当泛型类创建类型的新实例,请将new约束应用于类型参数,如下面的示例所示:
classItemFactory<T>where T :new()
{
public T GetNewItem()
{
return new T();
}
}
当与其他约束一起使用时,new() 约束必须最后指定:
publicclassItemFactory2<T>
where T : IComparable, new()
{
}
通过约束类型参数,可以增加约束类型及其继承层次结构中的所有类型所支持的允许操作和方法调用的数量。因此,在设计泛型类或方法时,如果要对泛型成员执行除简单赋值之外的任何操作或调用System.Object 不支持的任何方法,您将需要对该类型参数应用约束。
应用 where T : class 约束时,避免对类型参数使用 == 和 != 运算符,因为这些运算符仅测试引用同一性而不测试值相等性。即使在用作参数的类型中重载这些运算符也是如此。下面的代码说明了这一点;即使 String 类重载 == 运算符,输出也为 false。
public static void OpTest<T>(T s, T t) where T : class
{
System.Console.WriteLine(s == t);
}
static void Main()
{
string s1 = "foo";
System.Text.StringBuilder sb = new System.Text.StringBuilder("foo");
string s2 = sb.ToString();
OpTest<string>(s1, s2); //输出 false
}
这种情况的原因在于,编译器在编译时只知道 T 是引用类型,因此必须使用对所有引用类型都有效的默认运算符。如果必须测试值相等性,建议的方法是同时应用 where T : IComparable<T> 约束,并在将用于构造泛型类的任何类中实现该接口。
约束多个参数
可以对多个参数应用约束,并对一个参数应用多个约束,如下面的示例所示:
class Base { }
class Test<T, U>
where U : structwhere T : Base, new() { }
未绑定的类型参数
没有约束的类型参数(如公共类 SampleClass<T>{} 中的 T)称为未绑定的类型参数。未绑定的类型参数具有以下规则:不能使用 = 和 == 运算符,因为无法保证具体类型参数能支持这些运算符。可以在它们与 System.Object 之间来回转换,或将它们显式转换为任何接口类型。可以将它们与 null 进行比较。将未绑定的参数与 null 进行比较时,如果类型参数为值类型,则该比较将始终返回 false。
裸类型约束
用作约束的泛型类型参数称为裸类型约束。当具有自己的类型参数的成员函数必须将该参数约束为包含类型的类型参数时,裸类型约束很
用,如下面的示例所示:
class List<T>
{
void Add<U>(List<U> items) where U : T {/*...*/}
}
在上面的示例中,T 在 Add 方法的上下文中是一个裸类型约束,而在 List 类的上下文中是一个未绑定的类型参数。裸类型约束还可以在泛型类定义中使用。注意,还必须已经和其他任何类型参数一起在尖括号中声明了裸类型约束:
//naked type constraint
public class SampleClass<T, U, V> where T : V { }泛型类的裸类型约束的作用非常有限,因为编译器除了假设某个裸类
型约束派生自 System.Object 以外,不会做其他任何假设。在希望强制两个类型参数之间的继承关系的情况下,可对泛型类使用裸类型约束。
使用泛型集合类
泛型最常见的用途是泛型集合,从.NET2.0版类库开始提供一个新的命名空间 Sysstem.Collection.Generic,其中包含了几个新的基于泛型的集合类。使用泛型集合类可以提供更高的类型安全性,在某些情况下还可以提供更好的性能,尤其是在存储值类型时,这种优势更明显。
回顾一下常用的集合类型
标题 |
说明 |
数组集合类型 |
描述可以让数组作为集合来进行处理的数组功能。 |
ArrayList 和 List 集合类型 |
描述泛型列表和非泛型列表(最常用的集合类型)的功能。 |
Hashtable 和 Dictionary 集 合 类型 |
描述基于哈希的泛型和非泛型字典类型的功能。 |
已排序的集合类型 |
描述提供列表和集的排序功能的类。 |
队列集合类型 |
描述泛型和非泛型队列的功能。 |
堆栈集合类型 |
描述泛型和非泛型堆栈的功能。 |
HashSet 集合类型 |
描述泛型 System.Collections.Generic.HashSet(OfT) 集合 类型。 |
HashSet 和 LINQ Set 运算 |
描述 System.Collections.Generic.HashSet(OfT) 集合类型 提供的 Set 操作以及 LINQ Set 操作。 |
集合和数据结构 |
讨论 .NET Framework 中提供的各种集合类型,包括堆栈、队列、 列表、数组和结构。 |
.NET Framework 中的泛型 |
描述泛型功能,包括由 .NET Framework 提供的泛型集合、委托和 接口。 提供指向有关 C#、Visual Basic 和 Visual C++ 的功能 文档和支持技术(如反射)的链接。 |
常用的非泛型集合类及其对应的泛型集合类
ArrayList |
List<T> |
Hashtable |
Dictionary<TKey,TValue> |
Queue |
Queue<T> |
Stack |
Stack<T> |
SortedList |
SortedList<T> |
如下几种泛型集合类型没有对应的非泛型类型:
LinkedList 是一个通用的链接链表,它提供运算复杂为 O(1)的插入和移除操作。
SortedDictionary 是一个排序的字典,其插入和检索操作的运算复杂度为 O(log n),这使它成为 SortedList 的十分有用的替代类
型。KeyedCollection是介于列表和字典之间的混合类型,它提供了一种存储包含自己健的对象的方法。
一般情况下,建议使用泛型集合,因为这样可以获得类型安全的好处而不需要从集合类型派生并实现特定的成员。此外如果集合素为值类型,泛型集合类型的性能通常优于对应的非泛型集合类型(并优于从非泛型集合类型派生的类型),因为使用泛型时不必元素进行装箱。对应的非泛型集合和泛型集合的功能一般基本相同,但是一些泛型类型具有在非泛型集合类型中没用的功能。例如List<T>类(对应用非泛型ArrayList类)具有许多接受泛型委托(如允许指定搜索列表的方法的Predicate委托、表示操作每个列元素的Action委托和允许定义类型之间转换的Converter委托)的方法。List<T>类允许指定自己的用于排序和搜索列表的ICompare泛型接口实现。
下面看下List<T>和Dictionary<TKey,TValue>的程式代码:
using System;
using System.Collections.Generic;
namespace Generic_List
{
class Test
{
static void Main(string[] args)
{
List<string> li =new List<string>();
//与ArrayList一样默认容量大小为0,我在前面的文章还提到过ArrayList构造函数的效率问题
//用法也基本一样
Console.WriteLine("Capacity:{0}\n",li.Capacity);
li.Add("小沈阳");
li.Add("赵本山");
li.Add("陈佩斯");
li.Add("柳岩");
foreach (string sin li)
{
Console.WriteLine(s);
}
Console.WriteLine("Capacity:{0}\n Count:{1}",li.Capacity,li.Count);
Console.WriteLine(@"Contains(""柳岩"") is {0}", li.Contains("柳岩"));
Console.WriteLine(@"Insert(2,""宋丹丹"")");
li.Insert(2,"宋丹丹");
for (int i =0; i < li.Count; i++)
{
Console.WriteLine(li[i]);
}
Console.WriteLine("Capacity:{0}\n Count:{1}", li.Capacity, li.Count);
Console.WriteLine("Remove(\"陈佩斯\")");
li.Remove("陈佩斯");
foreach (string sin li)
{
Console.WriteLine(s);
}
//这里我们再次验证一下Capacity是否自动减小,实际是false
Console.WriteLine("Capacity:{0}\n Count:{1}", li.Capacity, li.Count);
li.TrimExcess();//功能等价于ArrayList的TrimToSize
Console.WriteLine("使用TrimExcess函数后,Capacity={0}",li.Capacity);
Console.WriteLine("使用Clear方法删除所有元素");
li.Clear();
Console.WriteLine("Capacity:{0}\n Count:{1}", li.Capacity, li.Count);
Console.ReadKey();
}
}
}
//////////////////////////////////////////////////////////////////////////////
usingSystem;
usingSystem.Collections.Generic;
namespaceGeneric_Dictionary
{
class Test
{
static void Main(string[] args)
{
Dictionary<string,string> doc =new Dictionary<string,string>();
doc.Add("操蛋曾某","初中数学老师");
doc.Add("蛋痛梁某","初中语文老师");
doc.Add("无奈黄某","高中英语老师");
doc.Add("邪恶徐某","无良缺德老师");
//如果试图添加一个已经存在的健,Add方法会抛出一个异常
try
{
doc.Add("邪恶徐某","老师中的垃圾车");
}
catch (ArgumentException)
{
Console.WriteLine("已经存在健->邪恶徐某");
}
Console.WriteLine("对应健->邪恶徐某的值(描述)为:{0}", doc["邪恶徐某"]);
//如果使用索引器来赋值时,如果健已经存在,那么就会修改对应的值,而不会引发异常
doc["邪恶徐某"] ="老师中的垃圾车";
Console.WriteLine("对应健->邪恶徐某的值(描述)为:{0}", doc["邪恶徐某"]);
//使用索引器添加一个元素。
doc["xxxx"] ="大学老师";
try
{
Console.WriteLine("对应doc[\"肖某\"]的值为:{0}", doc["肖某"]);
}
catch (KeyNotFoundException)
{
Console.WriteLine("无对应值!");
}
//当程序必须经常尝试获取字典中不存在的键值时,可以使用TryGetValue方法
//作为一种更有效的检索值方法。
string value ="";
if (doc.TryGetValue("ABC",out value))
{
Console.WriteLine("对应健\"ABC\"的值为{0}.",value);
}
else
{
Console.WriteLine("对应健\"ABC\"的值不存在");
}
//插入元素前可以先用ContainKey来判断健是否已经存在
if (!doc.ContainsKey("ABC"))
{
doc.Add("ABC","It's a Test");
Console.WriteLine("已经添加了对应健\"ABC\"的值{0}\n",doc["ABC"]);
}
Console.WriteLine("Remove(\"ABC\")");
doc.Remove("ABC");
if (!doc.ContainsKey("ABC"))
{
Console.WriteLine("没有找到ABC健");
}
foreach (KeyValuePair<string,string> k_vin doc)
{
Console.WriteLine("Key={0},Value={1}",k_v.Key,k_v.Value);
}
//值的集合
Dictionary<string,string>.ValueCollection Vco = doc.Values;
Console.WriteLine();
foreach (string sin Vco)
{
Console.WriteLine("Value={0}",s);
}
//键的集合
Dictionary<string,string>.KeyCollection Kco = doc.Keys;
Console.WriteLine();
foreach (string sin Kco)
{
Console.WriteLine("Key={0}", s);
}
Console.ReadKey();
}
}
}
自定义泛型集合类
与自定义的非泛型集合类一样,自定义泛型集合类也必须实现System.Collection.Generic命名空间中的IEnumerable<T>和IEnummerator<T>
接口,由于IEnumerable<T>和IEnummerator<T>分别继承了IEnumerable和IEnummerator接口,因此,实现 IEnumerable<T>接口的类同时,也必须实现IEnumerable接口,实现IEnummerator<T>接口的类同时,也必须实现IEnummerator接口。
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Collections;
namespaceBookstore
{
class store<T>:IEnumerable<T>,IEnumerator<T>
{
private string exceptionInfo ="索引无效,请确保调用了MoveNext";
private int index = -1;
protected List<T> books;
public store()
{
books=new List<T>() ;
}
public void Add(T book)
{
books.Add(book);
}
public void Remove(T book)
{
books.Remove(book);
}
//实现IEnumerator接口
public bool MoveNext()
{
return(++index<books.Count);
}
public void Reset()
{
index = -1;
}
public T Current
{
get
{
if (0 <= index && index < books.Count)
return books[index];
else
throw new IndexOutOfRangeException(exceptionInfo);
}
}
//一般来说,非泛型的Current方法可以简单的调用泛型Current方法
object System.Collections.IEnumerator.Current
{
get
{
return Current;
}
}
public IEnumerator<T> GetEnumerator()
{
Reset();
return this;
}
//一般来说,非泛型的GetEnumerator方法可以简单的调用泛型GetEnumerator方法
IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Dispose()
{
}
}
class Test
{
static void Main(string[] args)
{
store<string> store =new store<string>();
store.Add("语文");
store.Add("数学");
store.Add("英语");
Console.WriteLine("Store包含以下图书:");
foreach (string sin store)
{
Console.WriteLine(s);
}
Console.ReadKey();
}
}
}
泛型迭代器
与非泛型迭代器唯一的区别就是,泛型迭代器的返回类型必须是System.Collection.Generic.IEnumble<T>或者System.Collection.Generic.IEnumerator<T>,例如:
usingSystem;
usingSystem.Collections.Generic;
namespaceGeneric_yield
{
public class BookStore<T>
{
protected List<T> books;
public BookStore()
{
books =new List<T>();
}
public void Add(T book)
{
books.Add(book);
}
public void Remove(T book)
{
books.Remove(book);
}
public IEnumerator<T> GetEnumerator()
{
foreach (T bookin books)
{
yield return book;
}
}
}
class Test
{
static void Main(string[] args)
{
BookStore<string> store =new BookStore<string>();
store.Add("语文");
store.Add("数学");
store.Add("英语");
Console.WriteLine("Store包含以下图书:");
foreach (string sin store)
{
Console.WriteLine(s);
}
Console.ReadKey();
}
}
}