Linq Distinct and Custom Object Sort

The main purpose of this post is to show what's required in the main 3 .net languages for filtering and sorting.
I decided to put these two problems together because they presented themselves to me simultaneously .

There are many instances where I need to Sort custom objects and many instances where I need to filter out duplicates from a list of custom objects.

For filtering (Distinct), the effect happens with the framework finds unique values for the object.
For the Sort, the default comparer will not work on most custom objects, so some specifics need to be included.

Here are the two actions that need to be done to ensure success:

  1. Create the object and override the ToString(), Equals() and GetHashCode() methods.
    - I base the "uniqueness" on the value inside ToString() because it's easier
      than other techniques.
  2. For sorting, create a custom "Compare" class based on the IComparer interface
    - this class must contain a Compare method.

...and now for the example:
Some of the sorting code is commented out because it is only needed once.  I could have simply called the same method from each example, but I want to show it in each language.

I chose a very simple Person class containing two strings and an int.


First, here is the object:

namespace PersonObj
{
   public class CPerson
   {
      public string strFirstName {get; set;}
      public string strLastName { get; set; }
      public int intAge { get; set; }

      public CPerson()
      {
         strFirstName = "";
         strLastName = "";
         intAge = 0;
      }

      public CPerson(string strFirstName, string strLastName, int intAge)
      {
         this.strFirstName = strFirstName;
         this.strLastName = strLastName;
         this.intAge = intAge;
      }

      //1. Override the ToString (your choice on uniqueness)
      public override string ToString()
      {  // Used to create a unique vision of the object
         return
            strLastName.Trim() + ", " + //comma-space
            strFirstName.Trim() + ", " +//comma-space
            intAge.ToString();//suitable for printing
      }

      //2. Override the Equals so the comparison is done on the String
      public override bool Equals(object obj)
      {  // essential
         return obj.ToString().Equals(this.ToString());
      }

      //3. Override the GetHashCode, so the internals can find uniqueness
      public override int GetHashCode()
      {  // steal the HashCode from the ToString()
         return this.ToString().GetHashCode();
      }
   }
}


////////////////////////////////////////////////////////////
// Here's the main calling program.
using System;
using System.Collections.Generic;
using System.Linq;
//
using PersonObj;
using LinqDistinctCS;
using LinqDistinctCPP;
using LinqDistinctVB;
//
namespace LinqDistinct
{
   class Program
   {
      public static List<CPerson> lstPeople = new List<CPerson>()
      {
         new CPerson(){strLastName="Page", strFirstName="Neal", intAge=46},
         new CPerson(){strLastName="Page", strFirstName="Neal", intAge=46},
         new CPerson(){strLastName="Griffith", strFirstName="Del", intAge=45},
         new CPerson(){strLastName="Page", strFirstName="Susan", intAge=42},
         new CPerson(){strLastName="Griffith", strFirstName="Marie", intAge=34},
         new CPerson(){strLastName="Page", strFirstName="Marti", intAge=8},
         new CPerson(){strLastName="Page", strFirstName="Neal", intAge=6} //Junior
      };

      static void Main(string[] args)
      {
         Console.WriteLine("\n--- Before Distinct ---");
         lstPeople.ForEach(p => Console.WriteLine(p));

         Console.WriteLine("\n--- With Distinct CPP and Sort ---");
         CLinqDistinctCPP.TestDistinct(lstPeople);

         Console.WriteLine("\n--- With Distinct CSharp ---");
         CLinqDistinctCS.TestDistinct(lstPeople);

         Console.WriteLine("\n--- With Distinct VB ---");
         CLinqDistinctVB.TestDistinct(lstPeople);
      }
   }
}


 

C++ Example

// LinqDistinctCPP.h
#pragma once
using
namespace System;
using namespace System::Collections::Generic;
using namespace System::Linq;
using namespace PersonObj;
//
public ref class CComparePerson : IComparer<CPerson^>
{
public:
       /*************************************************\
       ** Even though I'm comparing strings, the method
       ** must take CPerson parameters and do the string
       ** comparison underneath.
       \*************************************************/
       virtual int Compare(CPerson^ p1, CPerson^ p2)
       {
              return p1->ToString()->CompareTo(p2->ToString());
       }
};


//
namespace LinqDistinctCPP {

       public ref class CLinqDistinctCPP
       {
       public:
              //
          static void DoPrint(CPerson^ p)
          {
                     Console::WriteLine(p);
          }
          //
              static void TestDistinct(List<CPerson^>^ lstPeople)
              {
                     // lstPeople->Sort(); ** Throws InvalidOperationException **
                     // The delegate must be in a separate class
                     lstPeople->Sort(gcnew CComparePerson()); // ** Good **
                    
                     // Note how C++ uses the Extension methods as static methods
                     Enumerable::ToList(
                           Enumerable::Distinct(lstPeople))
                           ->ForEach(gcnew Action<CPerson^>(DoPrint));
              }
       };
}


 

C# Example

 

using System;
using System.Collections.Generic;
using System.Linq;
using PersonObj;

namespace LinqDistinctCS
{
   /*
   public class CComparePerson : IComparer<CPerson>
   {
      virtual public int Compare(CPerson p1, CPerson p2)
          {
                 return p1.ToString().CompareTo(p2.ToString());
          }
   }
   */
   public class CLinqDistinctCS
   {
      public static void TestDistinct(List<CPerson> lstPeople)
      {
         // lstPeople.Sort(); ** Throws InvalidOperationException **
         // lstPeople.Sort(new CComparePerson()); // ** Good **
         // ...or...
         // lstPeople.Sort((p1, p2) => p1.ToString().CompareTo(p2.ToString()));
         lstPeople.Distinct().ToList().ForEach(p => Console.WriteLine(p));
      }
   }
}


VB Example

' Here's the VB Example
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports PersonObj
'
'Public Class CComparePerson
'Implements IComparer(Of CPerson)
'
'Public Function Compare(ByVal p1 As CPerson, _
'    ByVal p2 As CPerson) As Integer _
'Implements IComparer(Of CPerson).Compare
'   Return p1.ToString().CompareTo(p2.ToString())
'End Function
'End Class

Public Class CLinqDistinctVB
  
Public Shared Sub TestDistinct(ByVal lstPeople As List(Of CPerson))
     
'lstPeople.Sort() ** Throws InvalidOperationException **
      'lstPeople.Sort(New CComparePerson) ' ** Good **
      ' ...or...
      'lstPeople.Sort(Function(p1, p2) p1.ToString().CompareTo(p2.ToString()))
      lstPeople.Distinct().ToList().ForEach(AddressOf Console.WriteLine)
  
End Sub
End
Class



Here's the result:

 

 

 


C# (CSharp) Example

 

posted @ Monday, February 15, 2010 8:50 PM
Print

Comments on this entry:

# re: Linq Distinct and Custom Object Sort

Left by Marco at 12/10/2011 7:57 AM
Gravatar
Thank you!!! It works fine!!!

Your comment:



(not displayed)

 
 
 
 

Live Comment Preview: