Suppose we have a class: Product
Public class Product
{
Public string Id {get; set ;}
Public string Name {get; set ;}
} The Main function is as follows:
Static void Main ()
{
List <Product> products = new List <Product> ()
{
New Product () {Id = "1", Name = "n1 "},
New Product () {Id = "1", Name = "n2 "},
New Product () {Id = "2", Name = "n1 "},
New Product () {Id = "2", Name = "n2 "},
};
Var distinctProduct = products. Distinct ();
Console. ReadLine ();
}
The result of distinctProduct is:
Because Distinct compares the reference of the Product object by default, four data records are returned.
What should we do if we want to return a unique product with the Id?
The Distinct method has another overload:
// Compare the value by using the specified System. Collections. Generic. IEqualityComparer <T>
// Return non-repeating elements in the sequence.
Public static IEnumerable <TSource> Distinct <TSource> (this IEnumerable <TSource> source, IEqualityComparer <TSource> comparer); this overload receives an IEqualityComparer parameter.
If you want to filter by Id, you should create the ProductIdComparer class as follows:
Public class ProductIdComparer: IEqualityComparer <Product>
{
Public bool Equals (Product x, Product y)
{
If (x = null)
Return y = null;
Return x. Id = y. Id;
}
Public int GetHashCode (Product obj)
{
If (obj = null)
Return 0;
Return obj. Id. GetHashCode ();
}
}
Var distinctProduct = products. Distinct (new ProductIdComparer (); the result is as follows:
What if we want to filter duplicates by Name?
Obviously, you need to add another class ProductNameComparer.
Can I use generic classes ??
The new class PropertyComparer <T> inherits IEqualityComparer <T> as follows:
Public class PropertyComparer <T>: IEqualityComparer <T>
{
Private PropertyInfo _ PropertyInfo;
/// <Summary>
/// Obtain the PropertyInfo object through propertyName /// </summary>
/// <Param name = "propertyName"> </param>
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 )));
}
}
# Region IEqualityComparer <T> Members
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 ();
}
# Endregion
}
The main difference is that the rewritten Equals and GetHashCode use the attribute value comparison.
You only need:
// Var distinctProduct = products. Distinct (new PropertyComparer <Product> ("Id "));
Var distinctProduct = products. Distinct (new PropertyComparer <Product> ("Name "));
The result is as follows:
Why does Microsoft not provide the PropertyEquality <T> class?
According to the above logic, this class should not be very complicated. Careful students can find that PropertyEquality uses reflection in a large number. Every time you get the attribute value
_ PropertyInfo. GetValue (x, null );
As you can imagine, if there are many records to filter, the performance will undoubtedly be affected.
To improve performance, you can use the expression tree to change the reflection call to a delegate call,
The Code is as follows:
Public class FastPropertyComparer <T>: IEqualityComparer <T>
{
Private Func <T, Object> getPropertyValueFunc = null;
/// <Summary>
/// Obtain the PropertyInfo object through propertyName
/// </Summary>
/// <Param name = "propertyName"> </param>
Public FastPropertyComparer (string propertyName)
{
PropertyInfo _ 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 )));
}
ParameterExpression expPara = Expression. Parameter (typeof (T), "obj ");
MemberExpression me = Expression. Property (expPara, _ PropertyInfo );
GetPropertyValueFunc = Expression. Lambda <Func <T, object> (me, expPara). Compile ();
}
# Region IEqualityComparer <T> Members
Public bool Equals (T x, T y)
{
Object xValue = getPropertyValueFunc (x );
Object yValue = getPropertyValueFunc (y );
If (xValue = null)
Return yValue = null;
Return xValue. Equals (yValue );
}
Public int GetHashCode (T obj)
{
Object propertyValue = getPropertyValueFunc (obj );
If (propertyValue = null)
Return 0;
Else
Return propertyValue. GetHashCode ();
}
# Endregion
}
You can see that only getPropertyValueFunc (obj) is required to get the value.
When using:
Var distinctProduct = products. Distinct (new FastPropertyComparer <Product> ("Id"). ToList ();