委托的每个泛型参数都可标记为协变量或者逆变量。利用这个功能,可将泛型委托类型的一个变量转型为同一个委托类型的另一个变量,后者的泛型参数类型不同。泛型类型参数可以是以下任何一种形式。
1,不变量(invariant) 意味着泛型类型参数不能更改。
2,逆变量(contravariant)意味着泛型类型参数可以从一个基类型更改为该类的派生类。在C#中,用in关键字标记逆变量形式的泛型类型参数。逆变量泛型类型参数只出现在输入位置,比如作为方法的参数。
3,协变量(covariant)意味着泛型类型参数可以从一个派生类型更改为它的基类。在C#中,是用out关键字标记协变量形式的泛型类型参数。协变量泛型类型参数只能出现在输出位置。比如作为方法的返回类型。
例如,假定存在以下委托类型定义
public delegate TResult Fun<in T, out TResult>(T arg)
其中,泛型类型参数T用in关键字标记,这使它成为一个逆变量;泛型类型参数TResult 则用out关键字标记,这使它成为一个协变量。
所以,如果像下面这样声明一个变量:
Func<Object, ArgumentException> fn1 = null
就可将它转型为另一个泛型类型参数不同的Func类型:
Func<String, Exception> fn2= fn1; //这里不需要显式转型
Exception e =fn2("");
上述代码的意思是说:fn1变量引用一个方法,该方法获取一个Object , 返回一个ArgumentException . 而fn2 变量引用另一个方法,该方法获取一个String, 返回一个Exception 。由于可以将一个String传给期待着一个Object的方法(因为String从Object派生),而且由于可以获取“返回ArgumentException的一个方法”的结果,并将这个结果视为一个Exception(因为Exception是ArgumentException的基类),所以上述代码能正常编译,而且编译时能维持编译安全性。
使用要获取泛型参数和返回值的委托时,建议尽量为逆变性和协变性指定in和out关键字。这样做不会有不良反应,并使你的委托能在更多地情形中使用。
和委托相似,具有泛型参数的接口也可将它的类型参数标记为逆变量和协变量。下面的示例接口有一个协变量泛型类型参数:
public interface IEnumerator<out T> : IEnumberator {
Boolean MoveNext();
T Current {get;}
}
由于T是协变量,所以一下代码可以顺利编译和运行:
// 这个方法接受任意引用类型的一个IEnumerable
Int32 Count(IEnumerable<Object> collection){ .... }
// 以下调用向Count传递一个IEnumerable<String>
Int32 c = Count(new []{"jason"})