问题引出:在实际中遇到一个问题,要进行集合去重,集合内存储的是引用类型,需要根据id进行去重。这个时候linq 的distinct 就不够用了,对于引用类型,它直接比较地址。测试数据如下:

    class Person
{
public int ID { get; set; }
public string Name { get; set; }
}
List<Person> list = new List<Person>()
{
new Person(){ID=1,Name="name1"},
new Person(){ID=1,Name="name1"},
new Person(){ID=2,Name="name2"},
new Person(){ID=3,Name="name3"}
};

我们需要根据Person 的 ID 进行去重。当然使用linq Distinct 不满足,还是有办法实现的,通过GroupBy先分一下组,再取第一个数据即可。例如:

list.GroupBy(x => x.ID).Select(x => x.FirstOrDefault()).ToList()

通常通过GroupBy去实现也是可以的,毕竟在内存操作还是很快的。但这里我们用别的方式去实现,并且找到最好的实现方式。

一、通过IEqualityComparer接口

IEnumerable<T> 的扩展方法 Distinct 定义如下:

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

可以看到,Distinct方法有一个参数为 IEqualityComparer<T> 的重载。该接口的定义如下:

// 类型参数 T: 要比较的对象的类型。
public interface IEqualityComparer<T>
{
bool Equals(T x, T y);
int GetHashCode(T obj);
}

通过实现这个接口我们就可以实现自己的比较器,定义自己的比较规则了。

这里有一个问题,IEqualityComparer<T> 的 T 是要比较的对象的类型,在这里就是 Person,那这里如何去获得 Person 的属性 id呢?或者说,对于任何类型,我如何知道要比较的是哪个属性?答案就是:委托。通过委托,要比较什么属性由外部指定。这也是linq 扩展方法的设计,参数都是委托类型的,也就是规则由外部定义,内部只负责调用。ok,我们看最后实现的代码:

    //通过继承EqualityComparer类也是一样的。
class CustomerEqualityComparer<T,V> : IEqualityComparer<T>
{
private IEqualityComparer<V> comparer;
private Func<T, V> selector;
public CustomerEqualityComparer(Func<T, V> selector)
:this(selector,EqualityComparer<V>.Default)
{
} public CustomerEqualityComparer(Func<T, V> selector, IEqualityComparer<V> comparer)
{
this.comparer = comparer;
this.selector = selector;
} public bool Equals(T x, T y)
{
return this.comparer.Equals(this.selector(x), this.selector(y));
} public int GetHashCode(T obj)
{
return this.comparer.GetHashCode(this.selector(obj));
}
}

(补充1)之前没有把扩展方法贴出来,而且看到有朋友提到比较字符串忽略大小写的问题(其实上面有两个构造函数就可以解决这个问题)。这里扩展方法可以写为:

    static class EnumerableExtention
{
public static IEnumerable<TSource> Distinct<TSource,TKey>(this IEnumerable<TSource> source, Func<TSource,TKey> selector)
{
return source.Distinct(new CustomerEqualityComparer<TSource,TKey>(selector));
}
//4.0以上最后一个参数可以写成默认参数 EqualityComparer<T>.Default,两个扩展Distinct可以合并为一个。
public static IEnumerable<TSource> Distinct<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IEqualityComparer<TKey> comparer)
{
return source.Distinct(new CustomerEqualityComparer<TSource, TKey>(selector,comparer));
}
}

例如,要根据Person的Name忽略大小写比较,就可以写成:

list.Distinct(x => x.Name,StringComparer.CurrentCultureIgnoreCase).ToList(); //StringComparer实现了IEqualityComaparer<string> 接口

二、通过哈希表。第一种做法的缺点是不仅要定义新的扩展方法,还要定义一个新类。能不能只有一个扩展方法就搞定?可以,通过Dictionary就可以搞定(有HashSet就用HashSet)。实现方式如下:

        public static IEnumerable<TSource> Distinct<TSource,TKey>(this IEnumerable<TSource> source, Func<TSource,TKey> selector)
{
Dictionary<TKey, TSource> dic = new Dictionary<TKey, TSource>();
foreach (var s in source)
{
TKey key = selector(s);
if (!dic.ContainsKey(key))
dic.Add(key, s);
}
return dic.Select(x => x.Value);
}

三、重写object方法。能不能连扩展方法也不要了?可以。我们知道 object 是所有类型的基类,其中有两个虚方法,Equals、GetHashCode,默认情况下,.net 就是通过这两个方法进行对象间的比较的,那么linq 无参的Distinct 是不是也是根据这两个方法来进行判断的?我们在Person里 override 这两个方法,并实现自己的比较规则。打上断点调试,发现在执行Distinct时,是会进入到这两个方法的。代码如下:

class Person
{
public int ID { get; set; }
public string Name { get; set; } public override bool Equals(object obj)
{
Person p = obj as Person;
return this.ID.Equals(p.ID);
} public override int GetHashCode()
{
return this.ID.GetHashCode();
}
}

在我的需求里,是根据id去重的,所以第三种方式提供了最优雅的实现。如果是其它情况,用前面的方法更通用。

linq distinct 不够用了!的更多相关文章

  1. LINQ Distinct()

    using System; using System.Collections.Generic; using System.Linq; namespace LinqTest { class Progra ...

  2. linq Distinct 去除重复数据

    转载:http://www.cnblogs.com/ldp615/archive/2011/08/01/distinct-entension.html 只可惜linq默认不支持.Distinct(p ...

  3. C# IEqualityComparer 使用方法 Linq Distinct使用方法

    创建 IEqualityComparer的接口类必须实现Equals和GetHashCode方法 public class TipComparer : IEqualityComparer<Tip ...

  4. Linq Distinct List 去重复

    //调用 return producePlantlst.Distinct(new item_collection_DistinctBy_item1()).ToList(); //方法 public c ...

  5. linq Distinct 自定义去重字段

    一.定义 1.Falcon_PumpX_Equal_Comparer :类名,随便取名 2.IEqualityComparer:必须继承这个接口 3.Falcon_PumpX:需要去重的对象 4.IE ...

  6. Linq Distinct 自定义比较

    private class MyMenuComparer : IEqualityComparer { public bool Equals(ParMenu x, ParMenu y){ return ...

  7. Linq使用Distinct删除重复数据时如何指定所要依据的成员属性zz

    最近项目中在用Linq Distinct想要将重复的资料去除时,发现它跟Any之类的方法有点不太一样,不能很直觉的在呼叫时直接带入重复数据判断的处理逻辑,所以当我们要用某个成员属性做重复数据的判断时, ...

  8. Distinct删除重复数据时 自定义的方法比较【转】

    最近项目中在用Linq Distinct想要将重复的资料去除时,发现它跟Any之类的方法有点不太一样,不能很直觉的在呼叫时直接带入重复数据判断的处理逻辑,所以当我们要用某个成员属性做重复数据的判断时, ...

  9. Asp.net之LINQ入门视频教程

    当前位置: 主页 > 编程开发 > Asp.net视频教程 > Asp.net之LINQ入门视频教程 > http://www.xin1234.com/Program/Aspn ...

随机推荐

  1. Three.js 入门指南(核心对象)

    推荐大家可以看看这个:http://wenku.baidu.com/link?url=RQU2exzV_EF3GATc3bzQU2o9LGMuCmiN5nUJth5SLG3E2TrxtBLQodJU_ ...

  2. Intent(一.显示使用intent)

    大家都知道如果手机只有一个活动的应用,那这个应用也太简单了吧.如同网页一下,是有多个组成的,在C#中我们可以使用各程skip控件或代码,这里不再赘述.那么我们还是在当前的项目中创建一个名为Second ...

  3. android:Intent匹配action,category和data原则

    1.当你在androidmanifest里面定义了一个或多个action时 你使用隐式意图其他activity或者service时,规定你隐式里面的action必须匹配XML中定义的action,可以 ...

  4. 了解HTML CSS选择器操作和特性

    子选择器 在CSS样式表中, 有时候我们需要为一个选择器进行再次的选择, 比如要为某段落标签下的<span>标签进行样式设定(<span>标签必须为段落标签下的第一代子元素, ...

  5. 在CentOS7上安装JDK1.8

    在CentOS7上安装JDK1.8 1 通过 SecureCRT 连接到阿里云 CentOS7 服务器: 2 进入到目录 /usr/local/ 中: cd /usr/local/ 3 创建目录 to ...

  6. IRIS数据集的分析-数据挖掘和python入门-零门槛

    所有内容都在python源码和注释里,可运行! ########################### #说明: # 撰写本文的原因是,笔者在研究博文“http://python.jobbole.co ...

  7. <<你的灯亮着吗?>>读书笔记

    本书是美国计算机传奇人物杰拉尔德.温伯格和唐纳德.高斯所著,我在网上买到的2003年版的本书,发现本书用20则幽默的现代寓言故事,60幅精美插图,以及一系列的适当提问和建议,让我们的思考方式慢慢得以扩 ...

  8. SqlServer--用代码创建和删除数据库或表

    创建数据库,创建表,设置主键数据库的分离和附加MS SQLServer的每个数据库包含:1个主数据文件(.mdf)必须.1个事务日志文件(.ldf)必须.可以包含:任意多个次要数据文件(.ndf)多个 ...

  9. composer "Illegal offset type in isset or empty"报错解决方案

    最近更新了composer版本,即执行以下任一命令 composer selfupdate | composer self-update 再次执行 composer update -vvv 会出现“I ...

  10. mysql内置函数大全

    mysql 字符串函数用法集合 ASCII(str)返回字符串str的最左面字符的ASCII代码值. mysql> select ascii('d'); +------------+ | asc ...