购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

建议42:使用泛型参数兼容泛型接口的不可变性

让返回值类型返回比声明的类型派生程度更大的类型,就是“协变”。协变不是一种新出现的技术,在以往的编码中,我们已经在不自觉地使用协变。以下的代码就是一个不自觉应用协变的例子:


public Employee GetAEmployee(string name)

{

Console.WriteLine("我是雇员:"+name);

return new Programmer(){Name=name};//Programmer是Employee的子类

}


Programmer是Employee的子类,所以一个Programmer对象也就是一个Employee对象。方法GetAEmployee返回一个Programmer的对象,也就相当于返回了一个Employee对象。正是因为在FCL 4.0以前的版本中,协变是如此自然的一种应用,所以,我们很有可能写出这样的代码:


class Program

{

static void Main(string[]args)

{

ISalary<Programmer>s=new BaseSalaryCounter<Programmer>();

PrintSalary(s);

}

static void PrintSalary(ISalary<Employee>s)

{

s.Pay();

}

}

interface ISalary<T>

{

void Pay();

}

class BaseSalaryCounter<T>:ISalary<T>

{

public void Pay()

{

Console.WriteLine("Pay base salary");

}

}

class Employee

{

public string Name{get;set;}

}

class Programmer:Employee

{

}

class Manager:Employee

{

}


在PrintSalary这个方法中,方法接收的参数类型是ISalary<Employee>。于是,我们想当然地认为ISalary<Programmer>必然也是可以被PrintSalary方法接收的。实际却不然,这段代码编译会发生类似如下的错误:


无法从"ConsoleApplication4.ISalary<ConsoleApplication4.Programmer>"

转换为"ConsoleApplication4.ISalary<ConsoleApplication4.Employee>"


编译器对于接口和委托类型参数的检查是非常严格的,除非用关键字out特别声明(在建议43中会阐述),不然这段代码只会编译失败。要让PrintSalary完成需求,我们可以使用泛型类型参数:


static void PrintSalary<T>(ISalary<T>s)

{

s.Pay();

}


注意 可能会有读者注意到,本建议开头处指出“协变”是针对返回值而言的,但是所举的这个例子却并没有体现“返回值”这个概念。实际上,只要泛型类型参数在一个接口声明中不被用来作为方法的输入参数,我们都可姑且把它看成是“返回值”类型的。所以,本建议中这种模式是满足“协变”的定义的。但是,只要将T作为输入参数,便不满足“协变”的定义了。如:


interface ISalary<out T>

{

void Pay(T t);

}


编译会提示:


方差无效:类型参数"T"必须为"ConsoleApplication4.ISalary<T>.Pay(T)"上有效的逆变式。"T"为协变。 HZLvhSNyB3Md2WKWHglPkFzs5iXEwmlFhZEXyD97FjQ/2Rps+MmznaHK2xrHMRQ5


点击中间区域
呼出菜单
上一章
目录
下一章
×

打开