原文:How to use LINQ methods to compare objects of custom types http://blogs.msdn.com/csharpfaq/archive/2009/03/25/how-to-use-linq-methods-to-compare-objects-of-custom-types.aspx

说明:纯属学习备忘之用。

 

  在操作对象集合上,LINQ提供了一个很方便的语法和很多有用的方法。然而,要使对象被LINQ的比较方法(比如Distinct or Intersect)正确的处理,对象类型必须满足特定的要求。

让我们看看Distinct方法,distinct方法返回一个集合里面所有不同的对象。

List<int> numbers = new List<int> { 1, 1, 2, 3 };

var distinctNumbers = numbers.Distinct();

foreach (var number in distinctNumbers)

    Console.WriteLine(number);

 

The output is:

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方法。

    所以,之前那个例子中的Number class应该如下实现:

class Number: IEquatable<Number>

{

    public int Digital { get; set; }

    public String Textual { get; set; }

 

    public bool Equals(Number other)

    {

 

        // Check whether the compared object is null.

        if (Object.ReferenceEquals(other, null)) return false;

 

        // Check whether the compared object references the same data.

        if (Object.ReferenceEquals(this, other)) return true;

 

        // Check whether the objects’ properties are equal.

        return Digital.Equals(other.Digital) &&

               Textual.Equals(other.Textual);

    }

 

    // If Equals returns true for a pair of objects,

    // GetHashCode must return the same value for these objects.

 

    public override int GetHashCode()

    {

 

        // Get the hash code for the Textual field if it is not null.

        int hashTextual = Textual == null ? 0 : Textual.GetHashCode();

 

        // Get the hash code for the Digital field.

        int hashDigital = Digital.GetHashCode();

 

        // Calculate the hash code for the object.

        return hashDigital ^ hashTextual;

    }

}

     但是如果你的类型不能被修改,比如是在类库中(dll),这样你就不能让这个类去实现IEquatable<T>接口了。答案就是新建自己的 equality comparer类,并将comparer类的实例作为Distinct方法的参数传进去。

Equality comparer必须实现IEqualityComparer<T>接口,并提供GetHashCode和Equals方法的实现。

 

以下为Number类的 Equality comparer 类:

 

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;

    }

}

 

不要忘记把 自定义的comparer类的实例作为Distinct的输入参数:

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

当然,这个规则除了适用于Distinct方法,还适用于Constains,Except,Intersect和Union方法。通常地,如果你看到LINQ方法中有一个重载方法接受IEqualityComparer<T>为参数,那就说明你自定义的类型需要实现IEquatable<T> 或者创建自己的equality comparer.

其他资料(原文无此部分):

http://msdn.microsoft.com/en-us/library/ms131190.aspx

http://blogs.msdn.com/jaredpar/archive/2009/01/15/if-you-implement-iequatable-t-you-still-must-override-object-s-equals-and-gethashcode.aspx

posted on 2010-05-03 19:55  gracestoney  阅读(283)  评论(0编辑  收藏  举报