Variance变性
泛型的某个方面会让人感到奇怪,比如下面的代码是不合法的——
IList<string> strings = new List<string>();
IList<object> objects = strings;
第二个赋值是不允许的,因为strings和objects的元素类型并不一样。这样做有这充分的原因。如果允许那样写的话,你可能会写——
objects[0] = 5;
string s = strings[0];
这会允许将int插入strings列表中,然后将其作为string取出。这会破坏类型安全。
然而,对于某些接口来说上述情况并不会发生,尤其是不能将对象插入集合时。例如IEnumerable<T>就是这样的接口。如果改为——
IEnumerable<object> objects = strings;
这样就没法通过objects将错误类型的东西插入到strings中了,因为objects没有插入元素的方法。变性(variance)就是用于在这种能保证安全的情况下进行赋值的。结果就是很多之前让我们感到奇怪的情况现在可以工作了。
Covariance
协变性
在.NET 4.0中,IEnumerable<T>接口将会按照下面的方式进行定义——
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IEnumerator
{
bool MoveNext();
T Current { get; }
}
这些声明中的“out”指出T只能出现在接口的输出位置——如果不是这样的话,编译器会报错。有了这一限制,接口对于T类型就是“协变的”,这意味着如果A可以按引用转换为B,则IEnumerable<A>可以当作IEnumerable<B>使用。
其结果是,任何一个字符串序列也就是一个对象序列了。
这很有用,例如在LINQ方法中。使用上面的定义——
var result = strings.Union(objects);
之前这样做是不允许的,你必须做一些麻烦的包装,使得两个序列具有相同的元素类型。
Contravariance
逆变性
类型参数还可以具有“in”修饰符,限制它们只能出现在输入位置上。例如IComparer<T>——
public interface IComparer<in T>
{
public int Compare(T left, T right);
}
其结果有点让人迷惑,就是IComparer<object>可以作为IComparer<string>使用!这样考虑这个结果就会很有意义——如果一个比较器可以比较任意两个object,它当然也可以比较两个string。这种性质被称作“逆变性(contravariance)”。
泛型类型可以同时拥有带有in和out修饰符的类型参数,例如Func<...>委托类型——
public delegate TResult Func<in TArg, out TResult>(TArg arg);
很明显参数永远都是传入的,而结果永远只能是传出的。因此,Func<object, string>可以用作Func<string, object>。
具体时间代码见下面,在VS2010与 windows7的环境调试成功
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SimpleVariance { class Animal { } class Cat: Animal { } class Program { //要了解新的协方差和逆变代码为你做什么 //尝试删除或添加下面2行代码: delegate T Func1<out T>(); //out关键字的意思是说IEnumerable<T>中T只会被用在输出中,值不会被改变。这样将IEnumerable<string>转为IEnumerable<object>类型就是安全的。 delegate void Action1<in T>(T a); //in的意思正好相反,是说IComparer<T>中的T只会被用在输入中,这样就可以将IComparer<object>安全的转为IComparer<string>类型。 //前者被称为Co-Variance, 后者就是Contra-Variance。 static void Main(string[] args) { Func1<Cat> cat = () => new Cat(); Func1<Animal> animal = cat; Action1<Animal> act1 = (ani) => { Console.WriteLine(ani); }; Action1<Cat> cat1 = act1; Console.WriteLine(animal()); cat1(new Cat()); } } }