AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1422&pId=-1
Implement Sort and Custom Enumerator in Generic List
page
by Satheesh Babu
Feedback
Average Rating: 
Views (Total / Last 10 Days): 40941/ 53

Introduction

It is obvious that when we are designing business object or Entities for our application we will create its corresponding collections. In 1.x days we used ArrayList for creating a collection of object or entity. We can also create our own custom collection by inheriting the Arraylist or CollectionBase in those times. The main disadvantage of using ArrayList is that it can hold any type, which can cause some serious issue where a collection of similar objects are expected. This is the right place in 1.x to go for our own collection by inheriting the Arraylist or CollectionBase.

With the introduction of 2.0 Framework we got so many new useful features that make our development easy in our applications. Generic list is one of such feature which we can use for creating a collection of similar objects. We can create our collection with generic List<T>, where T can be our BO, which can be strongly typed. For example, List<Employee> will allow only Employee instance to be added to the list. Moving forward we will see how to implement sort to using generic list Sort overload methods and also we will implement a custom enumerator that iterates through the list with the use of yield keyword.

Things to Consider

To make things easily understandable I will use Customer object with Name and Age attributes as seen in Listing 1.

Listing 1 - Customer BO

class Customer
{
  private int _Age;
  private string _Name;
 
  public int Age
  {
    get
    {
      return _Age;
    }
    set
    {
      _Age = value;
    }
  }
  public string Name
  {
    get
    {
      return _Name;
    }
    set
    {
      _Name = value;
    }
  }
}

So the Generic list that can take Cutomer object can be defined as,

Listing 2 - Customer BO Generic List

 
List<Customer> cuslist = new List<Customer>();
Implementing Sort

To sort the collection, generic list provides sort a method with 4 overloads.

Listing 3 - Generic List Sort Methods

List.Sort () 
List.Sort (Generic Comparison) 
List.Sort (Generic IComparer) 
List.Sort (Int32, Int32, Generic IComparer)

In the coming sections of this article we will see the implementation of sort in generic list using the above 4 overloads.

1st Overload - List.Sort ()

This method will sort the members of the list using the default comparer that is implemented in the object. I would like to sort based on the name in Customers List.

To make this method work, Customer BO should inherit IComparable<Customer> and implement public int CompareTo(Customer  cus) method. So our final BO will look like:

Listing 4 - List.Sort () Implementation

class Customer: IComparable < Customer >
{
  //Members
  //1st Overload
  public int CompareTo(Customer cus)
  {
    return this.Name.CompareTo(cus.Name);
  }
}

Consider the following.

Listing 5 - List creation and Sorting

List < Customer > cuslist1 = new List < Customer > ();
Customer cus1 = new Customer();
cus1.Name = "Fatima";
cus1.Age = 23;
cuslist1.Add(cus1);
 
Customer cus2 = new Customer();
cus2.Name = "Evangeline";
cus2.Age = 24;
cuslist1.Add(cus2);
 
Customer cus3 = new Customer();
cus3.Name = "Damien";
cus3.Age = 27;
cuslist1.Add(cus3);
 
Customer cus4 = new Customer();
cus4.Name = "Cameroon";
cus4.Age = 21;
cuslist1.Add(cus4);
 
Customer cus5 = new Customer();
cus5.Name = "Babu";
cus5.Age = 20;
cuslist1.Add(cus5);
 
cuslist1.Sort();
foreach (Customer cus in cuslist1)
{
  Console.WriteLine(cus.Name + " " + cus.Age);
}

The output of the above code will be,

Babu 20
Cameroon 21
Damien 27
Evangeline 24
Fatima 23

2nd Overload - List.Sort (Generic Comparison)

The argument “Generic Comparison” is the Comparsion<T> delegate called Comparison Generic Delegate.

To understand the implementation of this overload method we should understand the generic comparison delegate first. The signature of the delegate is:

Listing 6 - List.Sort (Generic Comparison) syntax

public delegate int Comparison < T > (T x, T y)

To implement this sort method we should define a method with the above signature that compares the two objects and returns an integer value. Usage of this generic delegate to sort will prevent the need to inherit the BO with IComparable<T> interface. Refer to MSDN for more information.

Consider Customer BO in the section Things to Consider.

Here I am going to implement two sort methods with Generic Comparison Delegate’s signature, one for sort by name and the other for sort by Age. Here comes the implementation.

So our final BO will look like:

Listing 7 - Customer BO with Generic Comparison delegate

class Customer
{
  //Members
  public static int CompareCustomerName(Customer c1, Customer c2)
  {
    return c1.Name.CompareTo(c2.Name);
  }
  public static int CompareCustomerAge(Customer c1, Customer c2)
  {
    return c1.Age.CompareTo(c2.Age);
  }
}

How to use it?

To use the List.Sort(Generic Comparison) create a instance of Comparison<Customer> delegate and register it with CompareCustomerName and CompareCustomerAge method.

Listing 8 - List creation and Sorting

List < Customer > cuslist2 = new List < Customer > ();
 
cus1 = new Customer();
cus1.Name = "Tom";
cus1.Age = 23;
cuslist2.Add(cus1);
 
cus2 = new Customer();
cus2.Name = "Sanjay";
cus2.Age = 24;
cuslist2.Add(cus2);
 
cus3 = new Customer();
cus3.Name = "Mathew";
cus3.Age = 27;
cuslist2.Add(cus3);
 
cus4 = new Customer();
cus4.Name = "Nguyen";
cus4.Age = 21;
cuslist2.Add(cus4);
 
cus5 = new Customer();
cus5.Name = "Peter";
cus5.Age = 20;
cuslist2.Add(cus5);
 
Console.WriteLine("\nSort Based on Name");
Comparison < Customer > compnamedel = new Comparison < Customer >
  (Customer.CompareCustomerName);
cuslist2.Sort(compnamedel);
 
foreach (Customer cus in cuslist2)
{
  Console.WriteLine(cus.Name + " " + cus.Age);
}
 
Console.WriteLine("\nSort Based on Age");
Comparison < Customer > compagedel = new Comparison < Customer >
  (Customer.CompareCustomerAge);
cuslist2.Sort(compagedel);
 
foreach (Customer cus in cuslist2)
{
  Console.WriteLine(cus.Name + " " + cus.Age);
}

The output will be:

Sort Based on Name
Mathew 27
Nguyen 21
Peter 20
Sanjay 24
Tom 23
 
Sort Based on Age
Peter 20
Nguyen 21
Tom 23
Sanjay 24
Mathew 27

3rd Overload - List.Sort (Generic IComparer)

The signature of the method is:

Listing 9 - - List.Sort (Generic IComparer) syntax

List.Sort (Generic IComparer) 

This method sorts the generic list with the comparer given in the argument.

To implement the comparer implement IComparer<T> interface in a separate class use:

Listing 10 - - List.Sort (Generic IComparer) implementation

class CustomerNameSort : IComparer<Customer>
{
  public int Compare(Customer c1, Customer c2)
  {
    return c1.Name.CompareTo(c2.Name);
  }
}

Implement Compare() method as above to sort customers list based on name.

Usage

Listing 11 - List creation and Sorting

List < Customer > cuslist3 = new List < Customer > ();
 
cus1 = new Customer();
cus1.Name = "Louise";
cus1.Age = 23;
cuslist3.Add(cus1);
 
cus2 = new Customer();
cus2.Name = "Katie";
cus2.Age = 24;
cuslist3.Add(cus2);
 
cus3 = new Customer();
cus3.Name = "Viru";
cus3.Age = 27;
cuslist3.Add(cus3);
 
cus4 = new Customer();
cus4.Name = "Satheesh";
cus4.Age = 21;
cuslist3.Add(cus4);
 
cus5 = new Customer();
cus5.Name = "Naveen";
cus5.Age = 20;
cuslist3.Add(cus5);
 
Console.WriteLine("\nSort Based on Name");
CustomerNameSort esort = new CustomerNameSort();
cuslist3.Sort(esort);
 
foreach (Customer cus in cuslist3)
{
  Console.WriteLine(cus.Name + " " + cus.Age);
}

The output will be:

Sort Based on Name
Katie 24
Louise 23
Naveen 20
Satheesh 21
Viru 27

4th Overload - List.Sort (Int32, Int32, Generic IComparer)

The fourth generic overload sort method will be helpful when we like to sort our list with a range specified, i.e. we can say to sort a subset of the list. The syntax will be:

Listing 12 - List.Sort (Int32, Int32, Generic IComparer) Syntax

List.Sort (Int32, Int32, Generic IComparer)

The first argument will be the start position, the next argument is the number of elements to be sorted starting from the start position specified and the last is comparer to use.

To make this sort method to work we need to implement IComparer interface as we did in the 3rd Overload - List.Sort (Generic IComparer) section.

 The comparer for our Customer BO will be:

Listing 13 - IComparer<Customer> implementation

class CustomerNameSort : IComparer<Customer>
{
  public int Compare(Customer c1, Customer c2)
  {
    return c1.Name.CompareTo(c2.Name);
  }
}

Usage

I am creating an unsorted Customer list with length 5 as shown below and it is sorted by name using the generic list sort.

Listing 14 - List creation and Sorting

List < Customer > cuslist4 = new List < Customer > ();
cus1 = new Customer();
cus1.Name = "George";
cus1.Age = 23;
cuslist4.Add(cus1);
 
cus2 = new Customer();
cus2.Name = "Bush";
cus2.Age = 24;
cuslist4.Add(cus2);
 
cus3 = new Customer();
cus3.Name = "John";
cus3.Age = 27;
cuslist4.Add(cus3);
 
cus4 = new Customer();
cus4.Name = "Kennedy";
cus4.Age = 21;
cuslist4.Add(cus4);
 
cus5 = new Customer();
cus5.Name = "Clinton";
cus5.Age = 20;
cuslist4.Add(cus5);
 
Console.WriteLine("\nSort Based on Name with Range");
CustomerNameSort esort4 = new CustomerNameSort();
cuslist4.Sort(0, 3, esort4);
foreach (Customer cus in cuslist4)
{
  Console.WriteLine(cus.Name + " " + cus.Age);
}

The output will be:

Sort Based on Name with Range
Bush 24
George 23
John 27
Kennedy 21
Clinton 20
Where the original unsorted list is,
George 23
Bush  24
John 20
Kennedy 21
Clinton 20

Since we have given the start position as 0 and range as 3, the first 3 list elements are sorted using the comparer.

Using Anonymous Methods

C# 2.0 introduced a concept called Anonymous methods which we can use in the place of a delegate. An anonymous method is a method without any name. To understand better, a normal method is one which will have a name, return type optionally arguments and an access modifier. So, an anonymous method in C# 2.0 is a feature to have methods without name and it can be used in the place where there is a use of a delegate. I will implement anonymous method to sort the generic list in this section.

List.Sort (Generic Comparison) using Anonymous method:

I am creating List<Customner> with 5 customers and using Anonymous method to sort the list.

The above implementation of Generic Comparison delegate in Listing 8 - List creation and Sorting can be rewritten using Anonymous delegate.

Listing 15 - Anonymous method implementation         

Console.WriteLine("\nSort Based on Name");
 
Cuslist2.Sort(delegate(Customer c1, Customer c2)
{
  return c1.Name.CompareTo(c2.Name);
}
 
);
foreach (Customer cus in cuslist2)
{
  Console.WriteLine(cus.Name + " " + cus.Age);
}
 
Console.WriteLine("\nSort Based on Age");
 
Cuslist2.Sort(delegate(Customer c1, Customer c2)
{
  return c1.Age.CompareTo(c2.Age);
}
 
);
foreach (Customer cus in cuslist2)
{
  Console.WriteLine(cus.Name + " " + cus.Age);
}

The output will be:

Sort Based on Name
Mathew 27
Nguyen 21
Peter 20
Sanjay 24
Tom 23
Sort Based on Age
Peter 20
Nguyen 21
Tom 23
Sanjay 24
Mathew 27

We have implemented sorting for the generic list using all the 4 overloads.

In the coming sections we will implement custom enumeration feature for our generic list. However, the Generic list have its own enumerating feature that is exposed via GetEnumerator() method. This section will also use the same Customer object that is used in the above discussions. We will create a class called CustomerCollection.

Listing 16 - CustomerCollection Object

public class CustomerCollection
{
  List < Customer > list = new List < Customer > ();
  public List < Customer > Customers
  {
    set
    {
      list = value;
    }
  }
  public CustomerCollection(List < Customer > cus)
  {
    this.Customers = cus;
  }
}

To make the understanding simple we will create a CustomerCollection class that takes List<Customer> list and we will create a custom enumerator implementation in C# for iterating the generic list using one of the new keyword of C#2.0 called yield. For example, what if the user needs to get all the customers with age > 50 and customers with age < 50 for some business reason? In this case we need to iterate the list, check the age and can output the customer details. Like the general GetEnumerator() method in List<T>, we will implement 2 enumerators called GetSeniorCustomerEnumerator() and GetJuniorCustomerEnumerator() that fetches employees with age >50 and age < 50 respectively. To implement the enumerator we need to implement IEnumerable<T> interface.

What does the yield keyword do?

Yield keyword is used in GetEnumerator() method of IEnumerable interface and we do not need to explicitly implement the MoveNext(), Reset() and Current of IEnumerator interface like we do in 1.x days. Thus, C# is doing some the work on behalf of us when we use yield keyword. With this information we will jump into the implementation of custom enumerator.

Implementing Custom Enumerator

We will create two classes, namely SeniorCustomerEnumerator and JuniorCustomerEnumerator that implements IEnumerable<Customer> interface like below for iterating the customer list and fulfilling our requirement.

Listing 17 - Enumerator or Iterator implementation

public class SeniorCustomerEnumerator: IEnumerable < Customer >
{
  List < Customer > list = new List < Customer > ();
 
  public SeniorCustomerEnumerator(List < Customer > emplist)
  {
    list = emplist;
  }
 
  public IEnumerator < Customer > GetEnumerator()
  {
    for (int i = 0; i < list.Count; i++)
    {
      if (list[i].Age > 50)
      {
        yield return list[i];
      }
    }
  }
  IEnumerator IEnumerable.GetEnumerator()
  {
    return (GetEnumerator());
  }
}
 
public class JuniorCustomerEnumerator: IEnumerable < Customer >
{
  List < Customer > list = new List < Customer > ();
 
  public JuniorCustomerEnumerator(List < Customer > emplist)
  {
    list = emplist;
  }
 
  public IEnumerator < Customer > GetEnumerator()
  {
    for (int i = 0; i < list.Count; i++)
    {
      if (list[i].Age < 50)
      {
        yield return list[i];
      }
    }
  }
  IEnumerator IEnumerable.GetEnumerator()
  {
    return (GetEnumerator());
  }
}

We have created Custom enumerator class with the use of yield keyword and now it is time to update the CustomerCollection class that gives the enumerator object for iterating.

Listing 18 - Custom Enumerator or Iterator implementation

public class CustomerCollection
{
  List < Customer > list = new List < Customer > ();
  public List < Customer > Customers
  {
    set
    {
      list = value;
    }
  }
  public CustomerCollection(List < Customer > cus)
  {
    this.Customers = cus;
  }
  public SeniorCustomerEnumerator GetSeniorCustomerEnumerator()
  {
    SeniorCustomerEnumerator enume = new SeniorCustomerEnumerator(list);
    return enume;
  }
  public JuniorCustomerEnumerator GetJuniorCustomerEnumerator()
  {
    JuniorCustomerEnumerator enume = new JuniorCustomerEnumerator(list);
    return enume;
  }
}

The GetSeniorCustomerEnumerator() and GetJuniorCustomerEnumerator() can be used to iterate through the generic list and give the customers with age > 50 and age < 50 respectively. The following code shows how to use "eth" above custom iterator.

Listing 19 - Using the Custom Enumerator

CustomerCollection cuscoll = new CustomerCollection(cuslist);
 
Console.WriteLine("Senior Customers ");
 
SeniorCustomerEnumerator sen = new SeniorCustomerEnumerator(cuslist);
foreach (Customer cus in cuscoll.GetSeniorCustomerEnumerator())
{
  Console.WriteLine(cus.Name + "   " + cus.Age);
}
 
Console.WriteLine("Junior Customers ");
foreach (Customer cus in cuscoll.GetJuniorCustomerEnumerator())
{
  Console.WriteLine(cus.Name + "   " + cus.Age);
}

The output of the above code will be:

Senior Customers
Fatima   57
Evangeline   52
Cameroon   55
Junior Customers
Damien   49
Babu   24

We can also use the default GetEnumerator() method and filter the employees with age> 50 and age < 50.

Listing 20 - Other Way of Enumerating

Console.WriteLine("Senior Customer");
foreach (Customer cus in cuslist)
{
  if (cus.Age > 50)
  {
    Console.WriteLine(cus.Name + "   " + cus.Age);
  }
}
 
Console.WriteLine("Junior Customer");
foreach (Customer cus in cuslist)
{
  if (cus.Age < 50)
  {
    Console.WriteLine(cus.Name + "   " + cus.Age);
  }
}

We can create custom enumerator using the yield keyword when we implement our own custom collection and if it needs an iterator for filtering members specific to a business need like above.

Downloads
Conclusion

We have built a sorting feature for our generic list by using the sort overloads in generic list that best fits our need. Apart from sorting the generic list we can use the custom enumeration feature of 2.0 to iterate a custom collection or a generic list.

Download the source that is packed with this article for better understanding. Happy Coding!!!



©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-03-28 9:02:15 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search