怎么使用LINQ方法来比较自定义类型对象

来源:互联网 发布:echarts动态获取数据 编辑:程序博客网 时间:2024/05/22 01:39

LINQ提供了方便的语法和很多操作对象集合的有用的方法。但是,要让LINQ比较方法如DistinctIntersect能正确处理,类型必须要满足一些条件。

让我们看看Distinct方法,它从集合中返回所有的不重复对象。

List<int> numbers = new List<int> { 1, 1, 2, 3 };var distinctNumbers = numbers.Distinct();foreach (var number in distinctNumbers)    Console.WriteLine(number);

输出:

1

2

3

 

但是如果你想在你自定义类型对象的集合使用Distinct方法呢?例如,像这样:

 

class Number{    public int Digital { get; set; }    public String Textual { get; set; }} class Program{    static void Main(string[] args)    {       List<Number> numbers = new List<Number> {           new Number { Digital = 1, Textual = "one" },           new Number { Digital = 1, Textual = "one" } ,           new Number { Digital = 2, Textual = "two" } ,           new Number { Digital = 3, Textual = "three" } ,           };        var distinctNumbers = numbers.Distinct();        foreach (var number in distinctNumbers)                   Console.WriteLine(number.Digital);    }}

代码可以通过编译,但输出却不一样:

1

1

2

3

为什么会这样?答案在LINQ的实现细节里。要让Distinct方法正确处理,类型必须实现IEquatable<T>接口且提供它自己的Equals和GetHashCode方法。(译注:根据我的实验,其实只需正确的重写object的Equals和GetHashCode方法便可,并非必须实现IEquatable<T>)

 

那么,上个例子的Number类实际上需要看起来像这样: 

class Number: IEquatable<Number>{    public int Digital { get; set; }    public String Textual { get; set; }     public bool Equals(Number other)    {         // 检查被比较的对象是否为null。        if (Object.ReferenceEquals(other, null)) return false;         // 检查是否引用的相同对象。        if (Object.ReferenceEquals(this, other)) return true;         // 检查对象的属性是否相等。        // (译注:这里的Textual.Equals(other.Textual)使用静态方法        // object.Equlas或==更合适,以免Textual为null时抛出异常。        // Digital由于是值类型所以不存在这个问题。)        return Digital.Equals(other.Digital) &&               Textual.Equals(other.Textual);    }     // 如果比较两个对象是相等的,    // 那么这两个对象的GetHashCode方法必须返回一样的值。     public override int GetHashCode()    {        // 如果Textual字段不为空,则获取它的哈希值。        int hashTextual = Textual == null ? 0 : Textual.GetHashCode();         // 获取Digital字段的哈希值        int hashDigital = Digital.GetHashCode();         // 计算对象的哈希值。        return hashDigital ^ hashTextual;    }}



但假如你无法改变此类型呢?如果它在一个库里而你没有办法让此类型实现IEquatable<T>接口呢?答案是创建一个你自己的比较器然后将其通过参数传递给Distinct方法。

相等比较器必须实现IEqualityComparer<T>接口,且同样提供GetHashCodeEquals方法。

这里是怎么为原Number类实现相等比较器,大概像这样:

class NumberComparer : IEqualityComparer<Number>{    public bool Equals(Number x, Number y)    {        if (Object.ReferenceEquals(x, y)) return true;         if (Object.ReferenceEquals(x, null) ||            Object.ReferenceEquals(y, null))                return false;             return x.Digital == y.Digital && x.Textual == y.Textual;    }     public int GetHashCode(Number number)    {        if (Object.ReferenceEquals(number, null)) return 0;         int hashTextual = number.Textual == null            ? 0 : number.Textual.GetHashCode();         int hashDigital = number.Digital.GetHashCode();         return hashTextual ^ hashDigital;    }}


不要忘记将比较器传递给Distinct方法:

var distinctNumbers = numbers.Distinct(new NumberComparer());

当然,这个规则不仅仅适用于Distinct方法。例如,同样可以用于ContainsExceptIntersect,和Union方法。通常,如果你看到此LINQ方法有个接受IEqualityComparer<T>参数的重载,这可能表示对你自己的类型使用此方法时,你需要在你的类中实现IEquatable<T>接口或创建你自己的相等比较器(译注:同上,正确重写object的Equals和GetHashCode便可)

阅读全文
0 0
原创粉丝点击