1.泛型,为了适应通用编程,我们可以使用类型参数,利用特定的类型,定义我们需要的类。泛型就是一个抽象层,是类型的模板,用它定义具体的类型,才能实例化对外...
2.C#提供5种泛型:类、结构、接口、委托和方法。注意前面4个是类型,而方法是成员。
3.创建和使用常规的。非泛型的类的过程中有两个步骤:声明创建类并创建类的实例。但是泛型类不是实际的类,而是类的模板,所以我们必须先从它们构建实际的类类型,然后创建这个构建后的类类型。
4.声明泛型类:
在类名之后放置一组尖括号。
在尖括号中用逗号分隔的占位符字符串来表示希望提供的类型。这被叫做类型参数。
在泛型类声明的主体中使用类型参数来表示应该被替代的类型。
例如:
class SomeClass<T1,T2>
{
public T1 Somevar = new T1();
public T2 Othervar = new T2();
}
5.使用泛型类:
SomeClass<short, int> mySc1 = new SomeClass<short, int>();
SomeClass<int, long> mySc1 = new SomeClass<int, long>();
<>中的类型参数要使用真实类型代替.
6.类型参数的约束:
约束使用where子句列出。
每一个有线束的类型参数有自己的where子句。如果形参有多个约束,它们在where子句中使用逗号分隔。
有关where子句的要点:
它们在类型参数列表的关闭尖括号之后列出。它们不使用逗号或其他符号分隔。它们可以以任何次序列出。
示例:
class MyClass<T1, T2, T3>
where T2:Customer //只有Customer类型或从Customer继承的类型的类才能用作T2的实参,
where T3:IComparable //只有实现IComparable接口的类才能用于类型实参。
{
...
}
7.约束类型次序:
共有五种类型的约束,如下。
约束类型 描述
类名 只有这个类型的类或从它继承的类才能用作类型实参
class 任何引用类型,包括类、数组、委托和接口都可以用作实参
struct 任何值类型都可以被用作类型实参
Interfacename 只有这个接口或实现这个接口的类型才能用作实参。
new() 任何带有无参公共构造函数的类型都可以用作实参。这叫作构造函数约束
where子句可以任何次序列出。然而,where子句中的约束必须有特定的顺序,如下。
最多只能有一个主约束,如果有则必须放在第一们。(类名,class,struct是主约束)
可以有任意多的InterfaceName约束。
如果存在构造数约束,则必须放在最后。
8.泛型方法。
泛型方法有两个参数列表。封闭在圆括号内的方法参数列表,封闭在尖括号内的类型参数列表。
声明泛型方法,需要在方法名称之后和方法参数这前放类型参数列表;在方法参数列表后放可选的约束子句
public void PrintData<S,T>(S p, T t)where S:Person
在调用泛型方法,应该在方法调用时提供类型实参,如:
MyMethod<short,int>();
MyMethod<int, long>();
类型推断
int myInt = 5;
MyMethod<int>(myInt); //由于编译器可以从方法参数中推断类型参数,
MyMethod(myInt); //我们可以省略这样写
9.扩展方法和泛型类。
扩展方法出可以和泛型结合使用,它允许我们将类中的静态方法关联到不同的泛型类上。
泛型类的扩展方法:
必须声明为public static;
必须是静态类的成员;
第一个参数类型中必须有关键字词this,后面是扩展的泛型类的名字。
10.泛型结构
与泛型类相似,泛型结构可以有类型参数和约束。泛型结构的规则和条件与泛型类是一样的。
11.泛型委托,在委托名称后、委托参数列表之前的尖括号中放类型参数列表。
deledate R MyDelegate<T,R>(T value); //注意,在这里有两个参数列表:委托形参列表和类型参数列表。
类型参数的范围包括:返回值、形参列表、约束子句。
示例:
delegate void MyDelegate<T>(T value);
class Simple
{
static public void PrintString(string s)
{
Console.WriteLine(s);
}
static public void PrintUpperString(string s)
{
Console.WriteLine(s.ToUpper());
}
}
class Program
{
static void Main(string[] args)
{
MyDelegate<string> myDel = new MyDelegate<string>(Simple.PrintString);
myDel += Simple.PrintUpperString;
myDel("The There");
Console.ReadKey();
}
}
12.泛型接口。
接口也有泛型接口,泛型接口允许我们编写参数的接口成员返回类型是泛型类型参数的接口。泛型接口的声明和非泛型接口的声明差不多,
interface IMyIfc<T>{...}
class Simple<S>:IMyIfc<S>{...}
我们也可以在非泛型类型中实现泛型接口。
interface IMyIfc<T>{...}
class Simple :IMyIfc<int>,IMyIfc<string>
泛型接口的实现必须唯一。实现泛型类型接口时,必须保证类型实参组合不会在类型中产生两个重复的接口。
interface IMyIfc<T>{...}
class Simple<S> :IMyIfc<int>,IMyIfc<S> //错误,如果S是int类型将会和第一个接口发生冲突。
12.泛型协变和逆变
先看一个例子:
class Animal
{
public int Legs = 4;
}
class Dog : Animal { }
delegate T Factory<T>();
class Program
{
static Dog MakeDog()
{
return new Dog();
}
static void Main(string[] args)
{
Factory<Dog> dogMaker = new Factory<Dog>(MakeDog);
Factory<Animal> animalMaker = dogMaker; //编译错误。
Console.WriteLine(animalMaker().Legs.ToString());
Console.ReadKey();
}
}
由派生类型构造的委托赋值给由基础类型构造的委托,为什么编译会给错误呢。答:问题在于尽管Dog是Animal的派生类,但委托Factory<Dog>没有从委托Factory<Animal>派生。
我们执行animalMaker委托,调用代码就希望返回一个Animal对象的引用,如果返回指向Dog对象的引用也应该完全可以,因为根据赋值兼容性,指向Dog的引用就是指向Animal 的引用。
如果派生类只是用于输出值,那么这种结构化的委托有效性之间的常关系叫做协变。在C#4.0中使用关键字标记委托声明中的类型参数。
delegate T Factory<out T>();
这是逆变:
class Animal
{
public int Legs = 4;
}
class Dog : Animal { }
delegate void Actionl<in T>(T a);
class Program
{
static void ActOnAnimal(Animal a)
{
Console.WriteLine(a.Legs);
}
static void Main(string[] args)
{
Actionl<Animal> act1 = ActOnAnimal;
Actionl<Dog> dog1 = act1;
dog1(new Dog());
Console.ReadKey();
}
}
其实,如果类型参数只用作委托中方法的输入参数的话就可以了。因为即使调用代码传入了一个程序更高的洗派生类的引用,委托中的方法也中期望一个程序低一些的派生类的引用。当然,它也仍然接收并知道如何操作。
这种在期望传入基类时允许传入派生对象的特性叫做逆变。在C#4.0中可以在类型参数中显示保用in关键字来使用。
协变逆变总结:这两种情况在实际类型转换中是没有问题的,然而委托没有继承关系,所以在C#4.0中可以显示使用关键字out、in来使用。
补充:
协变和逆变可以应用到委托上,其实相同的原则也可以应用到接口上,可以在声明接口的时候使用out和in关键字。
有关变化的更多内容。
在创建委托的时候,赋值运算符右边的方法名还没有类型,编译器可以判断这个方法符合委托的类型,除非其返回类型是Dog而不是Animal。不过编译器很聪明可以明白这是协变关系,然后创建已构建的类型并且把它赋值给变量。
class Animal
{
public int Legs = 4;
}
class Dog : Animal { }
delegate T Factory<out T>();
class Program
{
static Dog MakeDog()
{
return new Dog();
}
static void Main(string[] args)
{
Factory<Animal> animalMaker = MakeDog; //隐式强制转换。
Factory<Dog> dogMaker = MakeDog;
Factory<Animal> animalMaker2 = dogMaker; //需要out标识符
Factory<Animal> animalMaker3 = new Factory<Dog>(MakeDog); //需要out标识符。
Console.WriteLine(animalMaker().Legs.ToString());
Console.ReadKey();
}
}
不需要in/out关键字实现协变和逆变的隐式强制转换在C#4.0之前就有了。
有关变化的其他一些重要事项如下:
1.变化处理的是使用派生类替换基类的安全情况,反之亦然。因此变化只适用于引用类型,因为值类型不可以派生。
2.显式变化使用in和out关键字只适用于委托和接口,不适用于类、结构和方法。
3.不包括in和out关键字类型参数的委托和接口叫做不变,这些类型参数不能用协变或逆变。