1.泛型
C#提供了5中泛型:类,结构,接口,委托和方法,注意前4个是类型,而方法是成员
2.泛型类
- 声明泛型类
- 在类名之后放置一组尖括号
- 在尖括号中用逗号分隔的占位字符串来表示希望提供的类型,这类叫做类型参数
- 在泛型类声明的主体中使用类型参数来表示应该替代的类型
class SomeClass<T1,T2>
{
public T1 SomeVar=new T1();
public T2 OtherVar=new T2();
}
- 创建构造类型
SomeClass<short,int>
- 创建变量和实例
SomeClass<short,int> mySc1=new SomeClass<short,int>();
var mySc2=new SomeClass<short,int>();
和非泛型类一样,引用和实例可以分开创建
SomeClass<short,int> myInst;
myInst=new SomeClass<short,int>
3.类型参数的约束
要让泛型变得更有用,我们需要提供额外的信息让编译器知道参数可以接受哪些类型,这些额外的信息叫做约束,只有符合约束的类型才能替代给定的类型参数,来产生构造类型。
1. Where子句
- 每一个有约束的类型参数有自己的where子句
- 如果形参有多个约束,它们在where子句中使用逗号分隔
- 它们在类型参数列表的关闭尖括号之后列出
- 它们不使用逗号或其他符号分隔
- 它们可以以任何次序列出
- where是上下文关键字,所以可以在其他上下文中使用
class Myclass<T1,T2,T3>
where T2:Customer//T2的约束
where T3:IComparable//T3的约束
{
...
}
- 约束类型和次序
约束类型 | 描述 |
---|---|
类名 | 只有这个类型的类或从它继承的类才能用作类型实参 |
class | 任何引用类型,包括类,数组,委托和接口都可以用作类型实参 |
struct | 任何值类型都可以用作类型实参 |
接口名 | 只有这个接口或实现这个接口的类型才能用作类型实参 |
new() | 任何带有无参公共构造函数的类型都可以用作类型实参,这叫做构造函数约束 |
where子句可以以任何次序列出,然而,where子句中的约束必须有特定的顺序
- 最多只能有一个主约束,如果有则必须放在第一位
- 可以有任意多的接口名约束
- 如果存在构造函数约束,则必须放在最后
class SortedList<S>
where S:IComparable<S>{...}
class LinkedList<M,N>
where M:IComparable<M>
where N:ICloneable{...}
class MyDictionary<KeyType,ValueType>
where keyType:IEnumerable,new(){...}
4.泛型方法
与其他泛型不一样,方法是成员,不是类型,泛型方法可以在泛型和非泛型类以及结构和接口中声明
1. 声明泛型方法
- 在方法名称之后和方法参数列表之前防止类型参数列表
- 在方法参数列表后放置可选的约束子句
public void PrintData<S,T>(S p,T t)where S:Person
{...}
- 调用泛型方法
MyMethod<short,int>();
MyMethod<int,long>();
推断类型
如果我们为方法传入参数,编译器有时可以从方法参数中推断出泛型方法的类型形参中用到的那些类型,例如
public void MyMethod<T>(T myVal){...}
int myInt=5;
MyMethod(myInt);
5.泛型结构
泛型结构的规则和条件与泛型类是一样的
6.泛型委托
泛型委托和非泛型委托非常相似,不过类型参数决定了能接受什么样的方法
delegate R MyDelegeate<T,R>(T value)
7.泛型接口
泛型接口允许我们编写参数和接口成员返回类型是泛型类型参数的接口
interface IMyIfc<T>
{
T ReturnIt(T inValue);
}
8.委托的协变和逆变
class Animal{public int Legs=4;}
class Dog:Animal{}
delegate T Factory<out T>();//协变关键字out
class Program
{
static Dog MakeDog()
{
return new Dog();
}
static void Main()
{
Factory<Dog> dogMaker=MakeDog;
Factory<Animal> animalMaker=dogMaker;
Console.WriteLine(animalMaker().Legs.ToString());
}
}
这样可以工作,因为在调用的时候,方法返回指向派生类型的对象的引用,派生类型同样指向其基类,调用代码可以正常工作。
class Animal{public int NumberOfLegs=4;}
class Dog:Animal{}
class Program
{
delegate void Action1<in T>(T a)//逆变关键字in
static void ActOnAnimal(Animal a)
{
Console.WriteLine(a.NumberOfLegs);
}
static void Main()
{
Action1<Animal> act1=ActOnAnimal;
Action1<Dog> dog1=act1;
dog1(new Dog());
}
}
这样可以工作,因为在调用的时候,调用代码传入恶派生类型的变量,方法期望的只是其基类,方法完全可以像以前那样操作对象的基类部分。
9.接口的协变和逆变
class Animal{public string Name;}
class Dog:Animal{};
interface IMyIfc<out T>
{
T GetFirst();
}
class SimpleReturn<T>:IMyIfc<T>
{
static void DoSomething(IMyIfc<Animal> returner)
{
Console.WriteLine(returner.GetFirst().Name);
}
static void Main()
{
SimpleReturn<Dog> dogReturner=new SimpleReturn<Dog>();
dogReturn.items[0]=new Dog(){Name="Avonlea"};
IMyIfc<Animal> animalReturner=dogReturner;
DoSomething(dogReturner);
}
}