19.5 泛型集合接口支持协变和逆变

关于泛型接口对协变和逆变的支持,请参阅第17章17.12节内容。这里主要介绍在.NET Framework 4.0的BCL中,泛型集合接口添加了对于协变和逆变特性的支持。

其中支持协变的接口包括:

❑IEnumerable<T>

❑IEnumerator<T>

❑IQueryable<T>

❑IGrouping<TKey,TElement>

支持逆变的接口包括:

❑IComparer<T>

❑IEqualityComparer<T>

❑IComparable<T>

代码清单19-17 展示了IEnumerable<T>接口中的协变支持带来的益处。Loop方法接受IEnumerable<Animal>类型的集合作为参数。因为接口支持协变,故也可以将该方法重用于IEnumerable<Dog>类型的集合,因为Dog继承Animal。

代码清单19-17 利用IEnumerable<T>接口中的协变特性示例


using System;

using System.Collections.Generic;

namespace ProgrammingCSharp4

{

public class CollectionSample

{

public static void Main()

{

IEnumerable<Dog>dogs=new List<Dog>(){new Dog()};

CollectionSample.Loop(dogs);

}

public static void Loop(IEnumerable<Animal>animals)

{

foreach(Animal animal in animals)

{

animal.Bark();

}

}

}

public interface Animal

{

void Bark();

}

public class Dog:Animal

{

public void Bark()

{

Console.WriteLine(“旺旺……”);

}

}

}


接下来,使用IEqualityComparer<T>接口为例说明逆变。先来定义两个类,一个是产品类(Product),一个是水果类(Fruit),如下所示。


public class Product

{

public string Name{get;set;}

public int Code{get;set;}

}

public class Fruit:Product{}


然后定义一个比较器,实现IEqualityComparer<T>接口,如下:


class ProductComparer:IEqualityComparer<Product>

{

public bool Equals(Product x,Product y)

{

if(Object.ReferenceEquals(x,y))return true;

if(Object.ReferenceEquals(x,null)||Object.ReferenceEquals(y,null))return false;

return x.Code==y.Code&&x.Name==y.Name;

}

public int GetHashCode(Product product)

{

if(Object.ReferenceEquals(product,null))return 0;

int hashProductName=product.Name==null?0:product.Name.GetHashCode();

int hashProductCode=product.Code.GetHashCode();

return hashProductName^hashProductCode;

}

}


因为该接口支持逆变,因此如下代码是合法的:


IEqualityComparer<Product>productComparer=new ProductComparer();

IEqualityComparer<Fruit>fruitComparer=productComparer;


代码清单19-18 是完整的代码。需要注意的是,在代码中使用了集合类型的Distinct扩展方法,该方法主要是去除集合中的重复元素,该集合需要实现IEnumerable<TSource>接口,Distinct方法的其中一个重载的签名如下:


public static IEnumerable<TSource>Distinct<TSource>(this IEnumerable<TSource>source,IEqualityComparer<TSource>comparer)


因为该扩展访问位于System.Linq.Enumerable静态类中,因此要使用该扩展方法需要添加对命名空间System.Linq的引用。注意,扩展方法我们曾在第10.11节讲过。

代码清单19-18 逆变接口IEqualityComparer<T>示例


using System;

using System.Collections.Generic;

using System.Linq;

namespace ProgrammingCSharp4

{

public class Product

{

public string Name{get;set;}

public int Code{get;set;}

}

public class Fruit:Product{}

class ProductComparer:IEqualityComparer<Product>

{

public bool Equals(Product x,Product y)

{

if(Object.ReferenceEquals(x,y))return true;

if(Object.ReferenceEquals(x,null)||Object.ReferenceEquals(y,null))

return false;

return x.Code==y.Code&&x.Name==y.Name;

}

public int GetHashCode(Product product)

{

if(Object.ReferenceEquals(product,null))return 0;

int hashProductName=product.Name==null?0:product.Name.GetHashCode();

int hashProductCode=product.Code.GetHashCode();

return hashProductName^hashProductCode;

}

}

public class CollectionSample

{

public static void Main()

{

Fruit[]fruits={new Fruit{Name="apple",Code=9},

new Fruit{Name="orange",Code=4},

new Fruit{Name="apple",Code=9},

new Fruit{Name="lemon",Code=12}};

IEqualityComparer<Product>productComparer=new ProductComparer();

IEqualityComparer<Fruit>fruitComparer=productComparer;

IEnumerable<Product>noduplicates=

fruits.Distinct(fruitComparer);

foreach(var product in noduplicates)

Console.WriteLine(product.Name+“"+product.Code);

}

}

}


上述代码的运行结果是:


apple 9

orange 4

lemon 12

请按任意键继续……


从结果可以看到达到了预期的目的,两个重复的值为apple的对象被过滤掉了一个。