Click here to Skip to main content
12,294,380 members (66,752 online)
Click here to Skip to main content
Add your own
alternative version


16 bookmarked

MultiList: .NET Generic List with Multiple Sort Orders

, 1 Feb 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
The MultiList class extends the functionality of the generic list. The MultiList class manages multiple sort orders on lists. It is best suited to object lists where searching is required on more than one criteria.
MultiList documentation screenshot


The MultiList class manages multiple sort orders on lists. It is best suited to object lists where searching for objects is required on more than one criteria. It is also suited when searches must be done on complete objects as well as properties within objects. The class employs a binary search algorithm to find objects in lists, as well as to insert objects in sorted order within lists.

This code sample demonstrates the declaration of a Person class, and the instantiation of a MultiList class to manage lists by three sort orders on the Person class. This documentation does not extensively demonstrate how to perform searches, additions, or deletions to the MultiList object; the reader should refer to the unit test code for this project for those examples. Here are some overview points about the MultiList class:

  • Any property that forms the basis of a sort must be immutable, but -
  • If a sort property must be modified, the client code should take care to remove the object with the Remove method before the modification, then reinsert it with the Add method after the modification
  • The class employs a binary search algorithm for efficiency, both for sorting and inserting objects into lists
  • Object properties that are sort criteria are not required to be unique
  • If a sort criteria contains duplicates in the list, a search will return the first in the series
  • No direct access to the underlying lists is allowed, but lists may be enumerated with foreach
  • The ActiveList property can be set before enumeration, to indicate which sort order to apply to subsequent enumerations
  • A single call to the Add method adds an object to all managed lists
  • A single call to the Remove method removes an object from all managed lists


The idea for this library came about as I was working on an unrelated project that manages a list on two different sort orders. Initially I used a list that was sorted on the most important sort order, and a clumsy property and filter which allowed iteration on the second sort order. I knew it was a total kludge, but it worked. Before beginning this library, I searched for this type of functionality in publicly available code, with no luck. It seems like a fairly common problem, easily encountered in routine scenarios:

  • I have an ordered list of customer names, and I would also like it sorted on city and state
  • I have an ordered list of file names, and I also want to sort in on extension, size, and date

The List<T> class in the System.Collections.Generic namespace provides all of the functionality to search and sort lists, but it comes up short in some ways:

  • Although it offers the ability to sort and to insert at a specified position, there is no mechanism to insert in sorted order without explicitly giving the index.
  • The BinarySearch method returns the first match found when duplicates are present, but not necessarily the first in a series of duplicates.
  • Although a list may be in sorted order, the methods Find, FindIndex, FindLast, and FindLastIndex perform linear searches to locate a match. On a sorted list, a binary search would be faster, followed by a linear search to the last duplicate for the FindLast and FindLastIndex methods.

The MultiList library addresses all these issues.

Using the Code

This code example demonstrates the declaration of a Person class, and the instantiation of a MultiList class to manage lists by three sort orders on the Person class. This documentation does not extensively demonstrate how to perform searches, additions, or deletions to the MultiList object; the reader should refer to the unit test code of this project for those examples.

The Person class is defined as follows:

public class Person : System.IEquatable<Person>
   public enum Profession

   private string _name;
   private int _age;
   private Profession _career;
   private static int _static_sequence = 0;
   private int _sequence;

   public Person (string name, int age,
      Profession career)
      _name = name;
      _age = age;
      _career = career;
      lock (typeof (Person))
         _sequence = ++_static_sequence;

   public string Name { get { return _name; } }
   public int Age { get { return _age; } }
   public Profession Career { get { return _career; } }
   public int Sequence { get { return _sequence; } }

   public bool Equals (Person p)
      return Sequence == p.Sequence;

Note the following points about the Person class:

  • The class implements the IEquatable<T> interface. The purpose of this is to define a standard of equality for the class, without relying on the GetHashCode method, which is the default measure of equality. This is important because you may not want to give a direct reference to your object to a client; you may want to give the client a cloned copy, which would have a different hash code.
  • The class employs a static sequence member and a sequence member at the object level to generate a unique ID. This ID is used to determine equality.

The next step is to define an enumerator and three custom classes which are to be used as arguments to the MultiList constructor.

// declare the enumerator which defines the sort orders
// that we want on the Person class
private enum PersonSortCriteria

public class NameComparer :
   public int Compare (Person p1, Person p2)
      return p1.Name.CompareTo (p2.Name);
   public int Compare (Person p, string name)
      return p.Name.CompareTo (name);

public class AgeComparer :
   public int Compare (Person p1, Person p2)
      return p1.Age - p2.Age;
   public int Compare (Person p, int age)
      return p.Age - age;

public class CareerComparer :
   public int Compare (Person p1, Person p2)
      return p1.Career.CompareTo (p2.Career);
   public int Compare (Person p, Person.Profession career)
      return p.Career.CompareTo (career);

The last step is to instantiate the three comparer objects and the MultiList class object. Note that the comparer object argument order matches the order of the PersonSortCriteria enumerator constants in the constructor.

NameComparer nameComparer = new NameComparer ();
AgeComparer ageComparer = new AgeComparer ();
CareerComparer careerComparer = new CareerComparer ();
MultiList<Person> ml =
   new MultiList<Person> (
      typeof (PersonSortCriteria), nameComparer,
      ageComparer, careerComparer);

After this, adding objects to the list is straightforward.

// add four Person objects to the MultiList
Person Jim = new Person ("Jim",    42,
ml.Add (Jim);
ml.Add (new Person ("Susan", 29,
ml.Add (new Person ("Arthur", 22,
ml.Add (new Person ("Ellen", 29,

A list can be searched for an index or for the actual object. It might be preferable to search for an index if you're not sure if a match will be found. If a match is not found, index searches return MultiList.NotFound. When a match is found, an index is returned that corresponds to the sort order of the search.

The following examples demonstrate the use of the index methods.

Searching for List Indexes

There are two types of searches that can be done on a MultiList object. The first type searches for an object with an existing Person object.

// return the index of Jim in the name sort list
int index = ml.FindIndex (PersonSortCriteria.Name, Jim);

// return the index of Jim in the age sort list
index = ml.FindIndex (PersonSortCriteria.Age, Jim);

The second type of search uses a data type that corresponds to a particular sort order.

// return the index of Susan in the name sort list
int index = ml.FindIndex<string>
   (PersonSortCriteria.Name, nameComparer, "Susan");

// return the index of the first Person with an age of 29
index = ml.FindIndex<int>
   (PersonSortCriteria.Age, ageComparer, 29);

// return the index of the first Person
// with a career of Barber
index = ml.FindIndex<Person.Profession>
   (PersonSortCriteria.Career, careerComparer,

The ActiveList property may be used to specify a particular sort order, which will apply to all subsequent methods which do not specify a sort order.

ml.ActiveList = PersonSortCriteria.Age;

// return the index of the first Person with an age of 29
int index = ml.FindIndex<int>
   (ageComparer, 29);

When an index is obtained, the object can be accessed in a couple of ways. In the previous code example, the ActiveList property was set to PersonSortCriteria.Age. If the Item indexer is used, it will return objects from the Age list.

// note that we first check to see that a valid index was
// returned, before accessing the list element.
if (index != MultiList.NotFound)
   Person match = ml[index];

You may also access any of the lists using the GetObject method.

int index = ml.FindIndex<string>
   (PersonSortCriteria.Name, nameComparer, "Susan");
if (index != MultiList.NotFound)
   Person match = ml.GetObject (PersonSortCriteria.Name,

Searching for Actual Objects

The Find methods return a reference to the object stored in the list. When you use one of the Find methods that return an actual object, you will have to do so in a try/catch block, or else use the TryFind methods.

Person match;
   match = ml.Find<string>
      (PersonSortCriteria.Name, nameComparer, "Susan");
catch (MultiListObjectNotFoundException)
   // not found

// set the active list to age, and use the
// TryFind method to search
ml.ActiveList = PersonSortCriteria.Age;
if (true == TryFind<string>
   (ageComparer, 29, out match))
   // a match was found

Searching for Multiple Objects

Several methods are available that return multiple objects or indexes when duplicates exist.

IndexRange range = ml.FindIndexes<int>
   (PersonSortCriteria.Age, ageComparer, 29);
if (range.Count > 0)
   // one or more matches was found

Person[] matches = ml.FindMultiple<int>
   (PersonSortCriteria.Age, ageComparer, 29);
if (matches.Length > 0)
   // one or more matches was found

Points of Interest

The unit test code for this project requires NUnit 2.4.3.


  • Enhanced - February 5, 2008: Added IndexRange object and iteration. This object simplifies the handling of multiple indexes returned from Find methods.
  • Initial release - January 20, 2008


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

United States United States
No Biography provided

You may also be interested in...

Comments and Discussions

GeneralSequence? [modified] Pin
Roger Alsing2-Feb-08 8:13
memberRoger Alsing2-Feb-08 8:13 
GeneralRe: Sequence? Pin
gogglin3-Feb-08 10:23
membergogglin3-Feb-08 10:23 
Hi Roger,
Thanks for your question. You're right, no interface is needed to determine equality. I wrote the code in that fashion to make a point, namely that equality is how you define it, not necessarily to be the same object. Classes that contain MultiList objects should consider single vs. multi thread issues, as well as public exposure of objects that they put in the list.

If the list is used in a single-threaded environment, maybe internally in some class, when the objects in the MultiList are not exposed externally, then the managed object class need not implement an Equals method. Since MultiList is a single-threaded library, as is the List<> class, a class which uses MultiList should sufficiently guard the objects of the list - not passing them on externally to clients. MultiList returns direct references to objects in the list.

For instance, if a class called MarketBasket has a MultiList list within it, and also exposes public methods that return the GroceryItem references back to clients, the integrity of the list is exposed. The client could change a property of GroceryItem (without class MarketBasket knowing) that is possibly a sort criteria property. Also, if your MarketBasket class is meant to be multithreaded, then you lose control of how long the client holds onto the GroceryItem reference, possibly conflicting with another thread that is doing something to it (modifying it, deleting it).

If managed objects need to be shared with external clients, the best strategy might be to create a clone of the GroceryItem object in the MarketBasket class, for public consumption. For instance, assume that the MarketBasket class implements a Find method that searches the MultiList, and if it finds the object, creates a clone of the GroceryItem object and returns that. The client is free to hold onto it as long as they like, possibly pass them around through web services or whatever. If the MarketBasket also implements a Remove method, taking a GroceryItem object as an argument, then a standard of equality must be established to determine which object is to be removed from the list.

The underlying List class allows objects to be removed by index position or by object reference. I find the object reference to be the only way to go, because in a multi-threaded environment, the list can grow or shrink by other thread's actions, so holding onto an index is not reliable. If the managed object (GroceryItem) implements System.IEquatable, that will be the standard of equality when List.Remove is called. If the interface isn't there, then GetHashCode will be used.

Hope this is a helpful explanation.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160525.2 | Last Updated 2 Feb 2008
Article Copyright 2008 by gogglin
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid