我一直在尝试通过我在互联网上找到的一些示例来理解协变的概念以及里氏替换原理。 这是我目前正在遵循的示例之一,它让我有点困惑,不明白什么不是协变。
让我们举个例子:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class EmployeeExtendedDetail : Employee
{
public string Address { get; set; }
public string Email { get; set; }
}
public class EmployeeRepository
{
public virtual Employee GetEmployeeDetialById(int id)
{
return new Employee();
}
}
public class EmployeeExtendedDetailRepository : EmployeeRepository
{
public override EmployeeExtendedDetail GetEmployeeDetialById(int id)
{
return new EmployeeExtendedDetail();
}
}
在子类型
EmployeeExtendedDetailRepository
中,方法GetEmployeeDetialById
的返回类型更改为EmployeeExtendedDetail
,遵循上面文章中的示例。
现在我对协变的理解是,例如,当我们可以将
List<derived>
的实例分配给 List<base>
的实例时,两个实体称为协变。
我想暂时保留通用类型和 out/in
参数,只是想从上面的示例中了解。
现在从上面的例子来看:
L1. EmployeeRepository empRepo1 = new EmployeeExtendedDetailRepository();
L2. Employee employee2 = empRepo1.GetEmployeeDetialById(1); // OK
L3. EmployeeExtendedDetail employee3 = empRepo1.GetEmployeeDetialById(1); //ERROR - cannot implicitly convert
L4. EmployeeExtendedDetail employee4 = (EmployeeExtendedDetail)empRepo1.GetEmployeeDetialById(1); // OK
L5. EmployeeExtendedDetailRepository empRepo2 = new EmployeeExtendedDetailRepository();
L6. Employee employee5 = empRepo2.GetEmployeeDetialById(1); // OK
L7. EmployeeExtendedDetail employee6 = empRepo2.GetEmployeeDetialById(1); // OK
从上面代码中empRepo1.GetEmployeeDetialById(1)
调用的
L1中将执行重写版本的方法
GetEmployeeDetialById
并且应该返回EmployeeExtendedDetail
和L2的类型,我们可以将子类型分配给超类型。这是否意味着它们是协变的? L6 也一样。
再次在 L3 中需要显式转换。
嗯,老实说,我根本无法理解什么不是协变的概念。
现在我对协变的理解是,例如,当我们可以将 List 的实例分配给 List 的实例时,两个实体称为协变。
这是对泛型类型方差的描述,但您的示例代码完全涉及另一个主题:协变返回,这是在C#9中引入的。
从上面的 L1 代码中调用
将执行重写版本的方法empRepo1.GetEmployeeDetialById(1)
并且应该返回GetEmployeeDetialById
的类型EmployeeExtendedDetail
这是部分正确的;确实会调用
GetEmployeeDetialById
的重写版本,并且返回对象的 runtime type 将为 EmployeeExtendedDetail
,但由于 empRepo1
的编译时类型为 EmployeeRepository
,因此返回的对象将是 Employee
。
LSP 声明可以使用更具体的类型来代替文件特定类型,但它不能改变行为。协方差在这里是安全的,因为
EmployeeExtendedDetail
源自 Employee
,但它不会改变 EmployeeRepository
变量的静态行为。
说明协变收益目的的示例:
// Use EmployeeExtendedDetailRepository as its specific type
EmployeeExtendedDetailRepository empRepo = new EmployeeExtendedDetailRepository();
EmployeeExtendedDetail employee = empRepo.GetEmployeeDetialById(1);
// Use EmployeeExtendedDetailRepository in place of EmployeeRepository
void SomeMethod(EmployeeRepository empRepo)
{
Employee employee = empRepo.GetEmployeeDetialById(1);
}
SomeMethod(new EmployeeExtendedDetailRepository());