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的对象被过滤掉了一个。