怎么使用LINQ方法来比较自定义类型对象
2017-07-24 10:33
561 查看
LINQ提供了方便的语法和很多操作对象集合的有用的方法。但是,要让LINQ比较方法如Distinct或Intersect能正确处理,类型必须要满足一些条件。
让我们看看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>接口,且同样提供GetHashCode和Equals方法。
这里是怎么为原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方法。例如,同样可以用于Contains,Except,Intersect,和Union方法。通常,如果你看到此LINQ方法有个接受IEqualityComparer<T>参数的重载,这可能表示对你自己的类型使用此方法时,你需要在你的类中实现IEquatable<T>接口或创建你自己的相等比较器(译注:同上,正确重写object的Equals和GetHashCode便可)。
让我们看看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>接口,且同样提供GetHashCode和Equals方法。
这里是怎么为原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方法。例如,同样可以用于Contains,Except,Intersect,和Union方法。通常,如果你看到此LINQ方法有个接受IEqualityComparer<T>参数的重载,这可能表示对你自己的类型使用此方法时,你需要在你的类中实现IEquatable<T>接口或创建你自己的相等比较器(译注:同上,正确重写object的Equals和GetHashCode便可)。
相关文章推荐
- 怎么使用LINQ方法来比较自定义类型对象
- [翻译]怎么使用LINQ方法来比较自定义类型对象
- [翻译]如何使用LINQ方法来比较自定义类型的对象
- Integer类型数据比较大小问题:(Integer定义的是对象,养成使用equals方法的好习惯)
- 用LinQ扩展方法,泛型扩展方法,实现自定义验证字符是否空、对象是否为null,及泛型约束使用,Action的使用
- 使用Object对象的toString()方法自定义判断数据类型方法
- 一个使用c++在lua中创建自定义数据类型的简易方法
- TreeSet 存储自定义类型时CompareTo 方法应该怎么写
- [原创]详述IComparer,IComparable接口,实现自定义方法比较对象大小并排序(C#)
- 比较application,session,cookies,viewstate四个对象区别?(从原理,使用范围, 存取方法等方面来讨论)
- oracle自定义类型使用方法
- 解决 ”不允许在查询中显式构造实体类型“问题及使用其他方法实现返回 List<Model对象>或者IQueryable<Model对象>对象
- java比较自定义对象相等的方法
- 缓存对象类型信息与使用GetType、typeof()的性能比较
- Java方法参数太多怎么办—Part 1—自定义类型
- list集合怎么转化成一个javaBean对象,及常见的使用方法(全)
- 单表多条件查询,匿名类型,匿名对象,嵌套查询,Linq序列转换后调用外部方法
- 一个使用c++在lua中创建自定义数据类型的简易方法
- Lambda表达式--使用方法语法的复杂查询: join (在单个 LINQ to Entities 查询中的两个结构上不兼容的初始化过程中出现类型)
- 往TreeSet中存入自定义对象,并且使用自定义排序方法(实现comparetor)