ASP.NET - Linq在使用Distinct去除重複資料時如何所指定依據的成員屬性

如何使用distinct?

基本程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//Person類別
public struct Person
{
public string ID { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}

//塞資料
var datas = new List<Person>();
int idx = 0;
for ( idx = 0; idx < 5; ++idx )
{
datas.Add(new Person() { ID = idx.ToString(), Name = "SLM" });
}
datas.Add(new Person() { ID = idx.ToString(), Name = "FuckSLM" });

//Console
private static void ShowDatas<T>(IEnumerable<T> datas)
{
foreach (var data in datas)
{
Console.WriteLine(data.ToString());
}
}

//然後直接用內建的Distinct過濾,發現根本沒用
var distinctDatas = datas.Distinct();
ShowDatas(distinctDatas);

// The example displays the following output:
// SLM
// SLM
// SLM
// SLM
// SLM
// FuckSLM

為了解決這個問題,我們必須要做個可依照Person.Name去做比較的Compare類別,該Compare類別必須實做IEqualityCompare.Equals與IEqualityCompare.GetHashCode方法,並在呼叫Distinct過濾時將該Compare物件帶入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//加入
public class PersonCompare : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x.Name.Equals(y.Name);
}

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

distinctDatas = datas.Distinct(new PersonCompare());
ShowDatas(distinctDatas);

// The example displays the following output:
// SLM
// FuckSLM

但是這樣做代表我們每次碰到新的類別就必須要實現對應的Compare類別,用起來十分的不便。因此有人就提出用泛型加上反射的方式做一個共用的Compare類別。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class PropertyComparer<T> : IEqualityComparer<T>
{
private PropertyInfo _PropertyInfo;

public PropertyComparer(string propertyName)
{
_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)));
}
}

public bool Equals(T x, T y)
{
object xValue = _PropertyInfo.GetValue(x, null);
object yValue = _PropertyInfo.GetValue(y, null);

if (xValue == null)
return yValue == null;

return xValue.Equals(yValue);
}

public int GetHashCode(T obj)
{
object propertyValue = _PropertyInfo.GetValue(obj, null);

if (propertyValue == null)
return 0;
else
return propertyValue.GetHashCode();
}
}
}

distinctDatas = datas.Distinct(new PropertyComparer<Person>("Name"));
ShowDatas(distinctDatas);

這樣的作法是減少了許多額外的負擔,但是感覺還是少了一條路,用起來也還是必須要建立Compare物件,而且反射也存在著效能的問題,如果每個元素都透過這個Compare去做判斷,感覺處理上也不是很漂亮。所以有人也意識到了這個問題,用擴充方法提供了一條我們比較熟悉的路,可以直接將Lambda帶入以決定元素要怎樣過濾。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);

參考1
參考2
參考3
參考4
參考5

0%