Friday, 8 April 2011

How to: Get a list <T> collection of distinct objects using lambda expression

In .net framwork 3.5 a developer could easily get the unique collection of objects from list<T>(where T is a class/object) using lambda expression. There are two options to achieve the distinct collection of objects.

Option #1: GroupBy()

var newlist = new [] {new { valueParam = "ABC", obj = 0 },     
   new { valueParam = "ABC", obj = 1 },     
   new { valueParam = "123", obj = 2 },     
   new { valueParam = "123", obj = 3 },     
   new { valueParam = "FOO", obj = 4 },     
   new { valueParam = "BAR", obj = 5 },     
   new { valueParam = "BAR", obj = 6 },     
   new { valueParam = "BAR", obj = 7 },     
   new { valueParam = "UGH", obj = 8 }, }; 

Let's use the groupby option to get the unique objects from the above list.

var listDistinct     
      = list.GroupBy(         
        i => i.valueParam,
        (key, group) => group.First()     
        ).ToArray(); 


You could see that two functions have been passed to the GroupBy(). The first is a key selector and the second gets only one item from each group. Over here I have used the First() method to have the top element in a group. If you want then you could also use the Last() too.

The results would be:

//{ valueParam = ABC, obj = 0 } 
//{ valueParam = 123, obj = 2 } 
//{ valueParam = FOO, obj = 4 } 
//{ valueParam = BAR, obj = 5 } 
//{ valueParam = UGH, obj = 8 }

As compare to Distinct, I believe that this solution is a little bit slow. Despite this disadvantage, the simplicity and flexibility is much more as compare to Distinct. It depends on the problem you're trying to solve.

Option #2: Distinct()

Suppose you have a list of duplicat objects of type DbParameter class and you would like to have distinct objects. You need to implement the comparer class of the object(T) which have been inherited from IEqualityComparer. Below code will be used to create LambdaComparer of type DbParameter. It is generic so that anyone could reuse it as is for any kind of object.


// <summary>
/// A generic comparer class allowing us to compare 2 objects 
//using simple Lambda expression.
/// </summary>
/// <typeparam name="T">The class type to compare.</typeparam>
public class LambdaComparer<T> : IEqualityComparer<T>
{
#region Data Members
private readonly Func<T, T, bool> _lambdaComparer;
private readonly Func<T, int> _lambdaHash;
#endregion

#region Constrctors

/// <summary>
/// Accepts just the Comparision function
/// </summary>
/// <param name="lambdaComparer">The comparision function.</param>
public LambdaComparer(Func<T, T, bool> lambdaComparer) :
this(lambdaComparer, o => 0)
{
}

/// <summary>
/// Accepts the Comparision & the HashCode generation functions
/// </summary>
/// <param name="lambdaComparer">The comparision function.</param>
/// <param name="lambdaHash">The HashCode generation function.</param>
public LambdaComparer(Func<T, T, bool> lambdaComparer,
Func<T, int> lambdaHash)
{
if (lambdaComparer == null)
throw new ArgumentNullException("lambdaComparer");

if (lambdaHash == null)
throw new ArgumentNullException("lambdaHash");

_lambdaComparer = lambdaComparer;
_lambdaHash = lambdaHash;
}

#endregion

#region Equals
/// <summary>
/// Compares the given objects for equality
/// </summary>
/// <param name="x">First object to compare.</param>
/// <param name="y">Second object to compare.</param>
/// <returns></returns>
public bool Equals(T x, T y)
{
return _lambdaComparer(x, y);
}
#endregion

#region GetHashCode
/// <summary>
/// Returns the HashCode of the given object
/// </summary>
/// <param name="obj">Object for which HashCode is required.</param>
/// <returns></returns>
public int GetHashCode(T obj)
{
return _lambdaHash(obj);
}
#endregion

Let's generate a list of distinct objects of type DbParameter using above LambdaComparer class.

List distinctDbParameter = lstDbParameter.Distinct
(new LambdaComparer((x, y) => 
x.ParameterName == y.ParameterName)).ToList(); 

I hope this will be helpful.

No comments:

Post a comment