17.11 扩展方法和泛型类
在第11章中我们学习了扩展方法,扩展方法允许我们在不修改类型的情况下对一个类型进行功能上的扩展,而且新的扩展方法可以在目标类的实例对象上,像调用实例方法那样进行调用。那么扩展方法能否和泛型类一起使用呢?答案是肯定的。
同样地,要使用扩展方法,必须做如下工作:
❑定义一个静态类,扩展方法也是静态方法,位于其中;
❑扩展方法的第一个参数以this修饰符为前缀,后跟要扩展的目标类型及其形参,这里的目标类型是泛型类。
其他的要求请读者自行参考第10.11节。接下来,我们在一段示例代码中定义了一个泛型类MyPrinter<T>,然后在同一个命名空间定义了一个静态类PrinterEx,在其中定义了扩展方法PrintEx,如代码清单17-13所示。
代码清单17-13 扩展方法和泛型类
1 namespace ProgrammingCSharp4
2{
3 public class MyPrinter<T>
4{
5 public T CurrentMsg
6{
7 get;
8 set;
9}
10
11 public MyPrinter(T data)
12{
13 CurrentMsg=data;
14}
15
16 public void Print(T msg)
17{
18 CurrentMsg=msg;
19 System.Console.WriteLine(msg);
20}
21}
22
23 static class PrinterEx
24{
25 public static void PrintEx<T>(this MyPrinter<T>printer)
26{
27 T msg=printer.CurrentMsg;
28
System.Console.WriteLine(“来自扩展方法:{0}”,printer.CurrentMsg);
29}
30}
31
32
class GenericsSample
33
{
34
public static void Main()
35
{
36
MyPrinter<string>printer=new MyPrinter<string>("hello world!");
37
printer.PrintEx();
38
}
39
}
40}
上述代码的运行结果为:
来自扩展方法:hello world!
17.12 协变和逆变
泛型接口中的泛型参数可以声明为协变参数或者逆变参数。我们在第15章介绍“委托”的时候学习过委托的协变和逆变,这里我们介绍下泛型接口的协变和逆变:
❑协变:泛型参数定义的类型只能作为方法的返回类型,不能用作方法参数的类型,且该类型直接或间接地继承自接口方法的返回值类型,称为协变。可以使用out关键字,将泛型类型参数声明为协变参数。
❑逆变:泛型参数定义的类型只能作为方法参数的类型,不能用作返回类型,且该类型是接口方法的参数类型的基类型,称为逆变。可以使用in关键字,将泛型类型参数声明为逆变参数。我们先来定义一个协变接口,如代码清单17-14所示。
代码清单17-14 具有协变参数的泛型接口
interface ISample1<out T>
{
T Function1();
}
class Sample1<T>:ISample1<T>
{
public T Function1()
{
Console.WriteLine("Worked……");
return default(T);
}
}
上述代码中,ISample1<out T>是一个协变接口,其泛型参数T是一个协变参数,Sample1<T>泛型类实现了该协变接口,如下代码体现了“协变”:
class BaseClass{}
class Child:BaseClass{}
ISample1<Child>sample1=new Sample1<Child>();①
ISample1<BaseClass>base1=sample1;②
ISample1<object>base2=base1;③
在上述代码中,①中sample1的协变参数的类型为Child(此时是协变实参),也就是①中sample1的接口方法返回类型为Child,而②中的接口方法返回类型为BaseClass,CLR会自动完成Child到BaseClass的隐式类型转换,此谓“协变”。同理,③也是合法的。
特别地,我们介绍一下代码清单17-14中的"default(T)"代码,在这里default关键字用以解决这样一个问题:在无法预参数化类型T是值类型还是引用类型的情况下,使用default关键字,对于引用类型会返回空引用(null),而对于数值类型则会返回0。
接下来,我们定义一个逆变接口,如代码清单17-15所示。
代码清单17-15 具有逆变参数的泛型接口
interface ISample2<in T>
{
void Function2(T arg);
}
class Sample2<T>:ISample2<T>
{
public void Function2(T arg)
{
Console.WriteLine(arg);
}
}
上述代码定义了一个逆变接口,以及一个实现了该接口的类。接下来,首先使用BaseClass作为类型实参来声明,并初始化一个变量sample2,然后再使用Child作为类型实参声明一个变量sample3,Child类是BaseClass类的派生类,可以基于逆变的规则将sample2赋给sample3变量。
ISample2<BaseClass>sample2=new Sample2<BaseClass>();
ISample2<Child>sample3=sample2;