C# Linq Distinct的使用

原文:http://www.it165.net/pro/html/201209/3778.html

这篇文章有点意思,就直接弄过来了


最近项目中在用Linq Distinct想要将重复的资料去除时,发现它跟Any之类的方法有点不太一样,不能很直觉的在呼叫时直接带入重复数据判断的处理逻辑,所以当我们要用某个成员属性做重复数据的判断时,就必需绕一下路,这边稍微将处理的方法做个整理并记录一下。
 
首先为了方便接下去说明,我们必须先来准备后面会用到的数据类别,这边一样用笔者最常用来示范的Person类别,内含两个成员属性ID与Name。

public struct Person
	{
		#region Property
		/// <summary>
		/// Gets or sets the ID.
		/// </summary>
		/// <value>The ID.</value>
		public string ID { get; set; }

		/// <summary>
		/// Gets or sets the name.
		/// </summary>
		/// <value>The name.</value>
		public string Name { get; set; } 
		#endregion


		#region Public Method
		/// <summary>
		/// Returns a <see cref="System.String"/> that represents this instance.
		/// </summary>
		/// <returns>
		/// A <see cref="System.String"/> that represents this instance.
		/// </returns>
		public override string ToString()
		{
			return Name;
		} 
		#endregion
接着准备要用来测试的资料,这边准备了十一个Person对象,前十个对象的名称都是Larry,第十一个对象的名称为LastLarry。期望后面可以透过Distinct将重复的Larry过滤掉。
...

var datas = new List<Person>();
int idx = 0;
for (idx = 0; idx < 10; ++idx)
{
	datas.Add(new Person() {ID = idx.ToString(), Name = "Larry" });
}
datas.Add(new Person() { ID = idx.ToString(), Name = "LastLarry" });
...
若是我们想直接用内建的Distinct函式来过滤数据。
...

var distinctDatas = datas.Distinct();
ShowDatas(distinctDatas);
...
private static void ShowDatas<T>(IEnumerable<T> datas)
{
	foreach (var data in datas)
	{
		Console.WriteLine(data.ToString());
	}
}
可以看到运行起来并不如我们所预期的,过滤出来的数据跟没过滤一样。


为了解决这个问题,我们必须要做个可依照Person.Name去做比较的Compare类别,该Compare类别必须实做IEqualityCompare.Equals与IEqualityCompare.GetHashCode方法,并在呼叫Distinct过滤时将该Compare对象带入。

distinctDatas = datas.Distinct(new PersonCompare());
ShowDatas(distinctDatas);
...
class PersonCompare : IEqualityComparer<Person>
{
	#region IEqualityComparer<Person> Members

	public bool Equals(Person x, Person y)
	{
		return x.Name.Equals(y.Name);
	}

	public int GetHashCode(Person obj)
	{
		return obj.Name.GetHashCode();
	}

	#endregion
}
运行起来就会是我们所期望的样子。


但是这样做代表我们每次碰到新的类别就必须要实现对应的Compare类别,用起来十分的不便。因此有人就提出用泛型加上反射的方式做一个共享的Compare类别。

public class PropertyComparer<T> : IEqualityComparer<T>
	{
		private PropertyInfo _PropertyInfo;

		/// <summary>
		/// Creates a new instance of PropertyComparer.
		/// </summary>
		/// <param name="propertyName">The name of the property on type T 
		/// to perform the comparison on.</param>
		public PropertyComparer(string propertyName)
		{
			//store a reference to the property info object for use during the comparison
			_PropertyInfo = typeof(T).GetProperty(propertyName,
		BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public);
			if (_PropertyInfo == null)
			{
				throw new ArgumentException(string.Format("{0} is not a property of type {1}.", propertyName, typeof(T)));
			}
		}

		#region IEqualityComparer<T> Members

		public bool Equals(T x, T y)
		{
			//get the current value of the comparison property of x and of y
			object xValue = _PropertyInfo.GetValue(x, null);
			object yValue = _PropertyInfo.GetValue(y, null);

			//if the xValue is null then we consider them equal if and only if yValue is null
			if (xValue == null)
				return yValue == null;

			//use the default comparer for whatever type the comparison property is.
			return xValue.Equals(yValue);
		}

		public int GetHashCode(T obj)
		{
			//get the value of the comparison property out of obj
			object propertyValue = _PropertyInfo.GetValue(obj, null);

			if (propertyValue == null)
				return 0;

			else
				return propertyValue.GetHashCode();
		}

		#endregion
	}
使用时只要带入泛型的型态与成原属性的名称,就可以产生出需要的Compare对象。

distinctDatas = datas.Distinct(new PropertyComparer<Person>("Name"));
    ShowDatas(distinctDatas);
这样的作法是减少了许多额外的负担,但是感觉还是少了一条路,用起来也还是必须要建立Compare对象,而且反射也存在着效能的问题,如果每个元素都透过这个Compare去做判断,感觉处理上也不是很漂亮。所以有人也意识到了这个问题,用扩充方法提供了一条我们比较熟悉的路,可以直接将Lambda带入以决定元素要怎样过滤。
public static class EnumerableExtender
{
	public static IEnumerable<TSource> Distinct<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
	{
		HashSet<TKey> seenKeys = new HashSet<TKey>();
		foreach (TSource element in source)
		{
			var elementValue = keySelector(element);
			if (seenKeys.Add(elementValue))
			{
				yield return element;
			}
		}
	}
}
使用上会好写许多。

distinctDatas = datas.Distinct(person => person.Name);
ShowDatas(distinctDatas);
若是不想加入额外的类别,我们也可以透过Group方式来达到类似的效果。

distinctDatas = from data in datas
	group data by data.Name into g
	select g.First();
ShowDatas(distinctDatas);





  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值