回顾下集合:
基本集合可以包含在类似ArrayList 这样的类中,但这些集合是没有类型化的,所以需要把object 项转换为集合中实际存储的对象类型。继承自System.Object 的任何对象都可以存储在ArrayList 中。
在C++中的泛型(在该语言中称为模扳)很早就被公认为是完成任务的最佳方式。
通过使用泛型,可以达到“一次编码,多次使用”的效果,从而极大地提高了代码的重用率,同时还可以获得强类型的支持,避免了隐式的装箱、拆箱,在一定程度上提升了应用程序的性能。(注:强类型是指程序中表达的任何对象所从属的类型必须能在编译时刻确定。强类型是针对类型检查的严格程度而言的,它指任何变量在使用的时候必须要指定这个变量的类型,而且在程序运行过程中这个变量只能存储这个类型的数据。因此,一个强类型不经过强制转换,它永远是这个数据类型,不允许隐式的类型转换。)
System.Collections.Generic名称空间,包含用于处理集合的泛型类型,使用得非常频繁,用using 语句配置它,使用时就不必添加限定符了。
好了,我们来看怎样定义泛型类型:
一、定义泛型类:
class MyGenericClass<T>
{
...
}
其中T 可以是任意标识符,只要遵循通常的C#命名规则即可,例如,不以数字开头等。但一般只使用T。 泛型类可以在其定义中包含任意多个类型,它们用逗号分隔开,例如:
class MyGenericClass<T1, T2, T3>
{
...
}
定义了这些类型之后,就可以在类定义中像使用其他类型那样使用它们。可以把它们用作成员变量的类型、属性或方法等成员的返回类型以及方法变元(argument)的多参数类型(parameter type)等。
例如:
class MyGenericClass<T1, T2, T3>
{
private T1 innerT1Object;
public MyGenericClass(T1 item)
{
innerT1Object = item;
}
public T1 InnerT1Object
{
get
{
return innerT1Object;
}
}
}
其中,类型T1 的对象可以传递给构造函数,只能通过InnerT1Object 属性对这个对象进行只读访问。注意,不能假定类提供了什么类型。例如,下面的代码就不会编译:
class MyGenericClass<T1, T2, T3>
{
private T1 innerT1Object;
public MyGenericClass()
{
innerT1Object = new T1();
}
...
}
我们不知道T1 是什么,也就不能使用它的构造函数,它甚至可能没有构造函数,或者没有可公共访问的默认构造函数。
1)default关键字:
要确定用于创建泛型类实例的类型,需要了解一个最基本的情况:它们是引用类型还是值类型。
若不知道这个情况,就不能用下面的代码赋予null 值:
public MyGenericClass()
{
innerT1Object = null;
}
如果T1 是值类型,则innerT1Object 不能取null 值,所以这段代码不会编译。
使用default 关键字(前面在switch 结构中使用过它)的新用法解决了它。该新用法如下:
public MyGenericClass()
{
innerT1Object = default(T1);
}
其结果是,如果innerT1Object 是引用类型,就结它赋予null 值;如果它是值类型,就结它赋予默认值。对于数字类型,这个默认值是0;而结构根据其各个成员的类型,以相同的方式初始化为0或null
2)约束类型 在定义泛型类时,我们可以对用户实例化类时所使用的类型参数的种类施加限制,如果用户尝试使用某个约束所不允许的类型来实例化类,则会产生编译错误。在C#中,这些限制被称为约束。
在类定义中,这可以使用where 关键字来实现:
class MyGenericClass<T> where T : constraint
{
...
}
其中 constraint 定义了约束。可以用这种方式提供许多约束,各个约束间用逗号分隔开:
class MyGenericClass<T> where T : constraint1, constraint2
{
...
}
还可以使用多个where 语句,定义泛型类需要的任意类型或所有类型上的约束:
class MyGenericClass<T1, T2> where T1 : constraint1 where T2 : constraint2
{
...
}
约束必须出现在继承说明符的后面:
class MyGenericClass<T1, T2> : MyBaseClass, IMyInterface
where T1 : constraint1 where T2 : constraint2
{
...
}
我们来看几种常见的约束:
<1>结构约束
格式:T(类型):struct
类型必须是值类型
<2>引用类型约束
格式:T(类型):calss
表示参数类型必须是引用类型,包括任何类、接口、委托或数组类型
<3>构造函数约束
格式:T(类型):new()
一般情况下,我们无法创建泛型类型参数的实例,然而,利用new()约束,就可以实例化一个泛型类型对象。使用new()约束时应注意:
(1)new()约束可以和其他约束一起使用,但它必须位于约束列表的末尾。
(2)new()约束仅允许使用无参构造函数来创建对象,即使同时存在其他构造函数,也不允许给类型参数的构造函数传递实参。
eg:
class A
{
public void A(){}
}
class B
{
public void B(int i){}
}
class C<T> where T:new()
{
T t=new T();
}
C<A> c=new C<A>();//正确
C<B> c=new C<B>();//错误,B没有无参构造函数
<4>基类约束
格式:T(类型):<基类名>
它表示类型实参要么是基类本身,要么是派生于该基类的类。此外,它还允许在泛型类中使用基类所定义的成员。例如,调用基类的方法或使用基类属性。
<5>接口约束
格式:T(类型):<接口名>
类型必须是接口或实现了接口
二、定义泛型接口:
interface MyFarmingInterface<T>
where T : Animal
{
bool AttemptToBreed(T animal1, T animal2);
T OldestInHerd { get; }
}
其继承规则与类相同。如果继承了一个基泛型接口,就必须遵循“保持基接口泛型类型参数的约束”等规则。
三、定义泛型方法:
在泛型方法中,返回类型和或参数类型由泛型类型参数来确定。例如:
public T GetDefault<T>()
{
return default(T);
}
回顾下前面提到的default 关键字,为类型T 返回默认值。这个方法的调用如下所示:
int myDefaultInt = GetDefault<T>();
在调用该方法时提供了类型参数T
四、定义泛型委托:
先来看一个委托
public delegate int MyDelegate(int op1, int op2);
要定义泛型委托,只需声明和使用一个或多个泛型类型参数格式如下:
public delegate T1 MyDelegate<T1, T2>(T2 op1, T2 op2) where T1: T2;
五、泛型集合:
泛型集合的功能非常强大,并且能够提高类的安全性和程序的运行效率,所以在实际的开发过程中得到了广泛的应用。下面我们重点认识List<T>和Dictionary<K,V>集合类。
1)List<T>:
创建T 类型对象的集合需要如下代码:
List<T> myCollection = new List<T>();
相关方法和属性的说明如下
int Count 该属性给出集合中项的个数
void Add(T item) 把一个项添加到集合中
void AddRange(IEnumerable<T>) 把多个项添加到集合中
IList<T> AsReadOnly() 给集合返回一个只读接口
int Capacity 获取或设置集合可以包含的项数
void Clear() 删除集合中的所有项
bool Contains(T item) 确定item 是否包含在集合中
void CopyTo(T[] array, int index) 把集合中的项复制到数组array 中,从数组的索引index 开始
IEnumerator<T> GetEnumerator() 获取一个IEnumerator<T>实例,用于迭代集合。注意,返回的接口强类型化为T,所以在foreach 循环中不需要类型转换
int IndexOf(T item) 获取item 的索引,如果集合中并未包含该项,就返回−1
void Insert(int index, T item) 把item 插入到集合的指定索引位置上
bool Remove(T item) 从集合中删除第一个item,并返回true;如果item 不包含在集合中,就返回false
void RemoveAt(int index) 从集合中删除索引index 处的项
List<T>还有个Item 属性,允许进行类似于数组的访问:
T itemAtIndex2 = myCollectionOfT[2];
2)Dictionary<K, V>
这个类型可以定义键/值对的集合,,这个类需要实例化两个类型,分别用于键和值,以表示集合中的各个项。
可以使用Keys 和Values 属性迭代集合中的键和值:
foreach (string key in things.Keys)
{
Console.WriteLine(key);
}
foreach (int value in things.Values)
{
Console.WriteLine(value);
}
还可以迭代集合中的各个项,把每个项作为一个KeyValuePair<K, V>实例来获取:
foreach (KeyValuePair<string, int> thing in things)
{
Console.WriteLine("{0} = {1}", thing.Key, thing.Value);
}
对于Dictionary<K, V>要注意的一点是,每个项的键都必须是唯一的。如果要添加的项的键与已有项的键相同,就会抛出ArgumentException 异常。
可以使用不区分大小写的方法来比较字符串键:
Dictionary<string, int> things =New Dictionary<string,int>(StringComparer.CurrentCultureIgnoreCase);
常用发方法和属性如下
Keys属性:获取Dictionary中键的集合
Values:获取Dictionary中值的集合
void Add(T item):将指定的键值添加到Dictionary
void Clear():从Dictionary中移除所有的键和值
void Remove(T item):从Dictionary中移除指定的键和值
拓展:
可空类型
我们知道值类型(大多数基本类型,例如,int、double 和所有的结构)区别于引用类型(string 和所有的类)的一种方式:值类型必须包含一个值,它们可以在声明之后、赋值之前,在未赋值的状态下存在,但不能以任何方式使用。而引用类型可以是null。
有时让值类型为空是很有用的(尤其是处理数据库时),泛型使用System.Nullable<T>类型提供了使值类型为空的一种方式。例如:
System.Nullable<int> nullableInt;
这行代码声明了一个变量nullableInt,它可以拥有int 变量能包含的任意值,还可以拥有值null。
所以可以编写如下的代码:
nullableInt = null;
如果nullableInt 是一个int 类型的变量,上面的代码是不能编译的。
前面的赋值等价于:
nullableInt = new System.Nullable<int>();
与其他任何变量一样,无论是初始化为null(使用上面的语法),还是通过给它赋值来初始化,都不能在初始化之前使用它。
多态性允许把派生类型的对象放在基类型的变量中