建议42:使用泛型参数兼容泛型接口的不成变性
让返回值类型返回比声明的类型派生水平更大的类型,就是“协变”。如:
public Employee GetAEmployee(string name) { Console.WriteLine("我是雇员:"+name); return new Programmer() { Name = name };//Programmer是Employee的子类 }
Programmer是Employee的子类,所以Programmer东西也是Employee东西。要领GetAEmployee返回一个Programmer的东西,也就是相当于返回一个Employee东西。
由于协变是一种如此自然的应用,我们很可能写出如下代码:
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要领接收的。事实却不然,代码编译会通不过:
无法从“MyTest.ISalary<MyTest.Programmer>”转换为“MyTest.ISalary<MyTest.Employee>”
编译器对付接口和委托类型参数的查抄长短常严格的,除非用关键字out出格声明,不然这段代码只会编译掉败。要让PrintSalary完成需求,,我们可以使用泛型类型参数:
static void PrintSalary<T>(ISalary<T> s) { s.Pay(); }
注意:建议开头指出“协变”是针对返回值而言的,但是所举的这个例子并没有浮现“返回值”这个观点。实际上,只有泛型类型参数在一个接口声明中不被用来作为要领的输入参数,我们就临时把它当作是“返回值”类型的。所以,本建议中这种模式是满足“协变”界说的。但是,只要将T作为输入参数,就不满足“协变”界说了。