在上面的几篇文章中,我们介绍了有关数组的 Variance、接口和委托的 Covariance 的概念和基本用法。本文介绍在 Variance 上的另外一种情况,即 Contra-variance。
Contra-variance 是一种将泛型类型从大到小转换的场景。 有些时候对于一个泛型委托,我们常常需要对其类型参数 T 进行引用转换,以便于实现 Composite 或者 Facade 模式。设 D<T1> 和 D<T2> 是两个泛型委托 D<> 的实例,T1 和 T2 具备继承关系,并且 T1 < T2,则如果允许 D<T2> 隐含转换为 D<T1>,则称为 D<> 支持 Cotra-variance。
支持 Contra-variance 的泛型类型可以表示为 X<in T> 或者 X<T->,其中 X 是接口或者是委托。
一个简单的例子
这个实例说明如何利用委托处理根据学生对象类别来选择执行存储数据的场景。请参考下面的代码。
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:6: namespace Demo
7: {8: class Program
9: {10: static void Main(string[] args)11: {12: PrimaryStudent s1 = new PrimaryStudent();
13: JuniorStudent s2 = new JuniorStudent();
14: SeniorStudent s3 = new SeniorStudent();
15:16: // Define the generic save handler.
17: SaveFunc<Student> saveFunc = s => Console.WriteLine(s.Type);18:19: // Set save delegates.
20: StudentDataStore<Student> dataStore1 = new StudentDataStore<Student>();
21: dataStore1.SaveHandler = saveFunc;22: dataStore1.Save(s1);23:24: StudentDataStore<JuniorStudent> dataStore2 = new StudentDataStore<JuniorStudent>();
25: // Contra-variance.
26: dataStore2.SaveHandler = saveFunc;27: dataStore2.Save(s2);28:29: // No variance.
30: dataStore2.Save((JuniorStudent)(Student)s3);31: }32: }33:34: enum StudentType
35: {36: Primary,37: JuniorHigh,38: SeniorHigh,39: College,40: Unknown41: }42:43: delegate void SaveFunc<in T>(T student) where T : Student;44:45: class StudentDataStore<T> where T : Student
46: {47: public virtual void Save(T student)48: {49: if (SaveHandler != null)50: {51: SaveHandler(student);52: }53: }54: public SaveFunc<T> SaveHandler { get; set; }55: }56:57: class Student
58: {59: public Guid Id { get; set; }60: public string Name { get; set; }61: public string ControlNumber { get; set; }62: public virtual StudentType Type { get; set; }63: }64:65: class PrimaryStudent : Student
66: {67: public override StudentType Type68: {69: get
70: {71: return StudentType.Primary;
72: }73: }74: }75:76: class JuniorStudent : Student
77: {78: public override StudentType Type79: {80: get
81: {82: return StudentType.JuniorHigh;
83: }84: }85: }86:87: class SeniorStudent : Student
88: {89: public override StudentType Type90: {91: get
92: {93: return StudentType.SeniorHigh;
94: }95: }96: }97: }98:
程序首先创建了三个 Student 的实例 s1, s2 和 s3,然后针对于不同的 Student 类型声明了两个 StudentDataStore,并将 SaveHandler 初始化为已经声明好的一个泛型委托的实例 saveFunc。注意,saveFunc 的类型为 SaveFunc<Student>,由于该类型被声明为 SaveFunc<in T>,支持 Contra-variance(47 行)。s1.SaveHandler 类型为 SaveFunc<Student>,与赋值的右值类型相同。因此,对 s1.SavezHandler 的赋值有效(21 行)。
我们再来看看对于 s2.SaveHandler 的赋值(26 行 )。s2.SaveHandler 的类型为 SaveFunc<JuniorStudent>,saveFunc 的类型仍旧未 SaveFunc<Student>,这里产生了 Contra-variance,从大类型 SaveFunc<Student> 转换为小类型 SaveFunc<JuniorStudent>。
最后看看 s3,s3 类型为 SeniorStudent,而 dataStore2.Save() 方法需要一个 JuniorStudent 类型的参数,因此必须对 s3 进行强制引用转换才能达到目的。这里没有 Contra-variance。
总结
Contra-variance 往往在泛型委托上使用,它能很好的解决一些常用设计模式中需要解决的问题,更大程度上提高了泛型的应用宽度和价值。到目前为止,我们的关于 Variance 特性的介绍就告一段落了,如果大家还有什么更深的问题,欢迎来信交流。下一篇文章我们将介绍 C# 4.0 中的命名和可选参数。