Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to create LINQ like functionality using Predicate Delegates and Anonymous Methods

0.00/5 (No votes)
6 Jan 2008 1  
Learn how to use Predicate Delegates, Anonymous Methods, Generics, Action Delegate to implement LINQ like WHERE and ORDERBY

Introduction

Today, I would be your guide on our way to empower custom collections (i.e. Employee, Order, Account, etc) with LINQ like functionality of Where and OrderBy (You can always match them to traditional SQL Query Syntax). And, help you in understanding what we can achieve by just knowing some newer C# Functionalities - Predicate Delegate, Action Delegate, Anonymous Methods and Generics.

Before we step into creating this kind of functionality, let's understand few basic concepts first:

  1. Predicate Delegate: We use a Predicate delegate in lieu of a special delegate(function pointer) which takes only one argument and returns a boolean value. This type of delegate is specially used by .NET Framework List classes. For understanding this better, read my previous post on Predicate Delegate.
  2. Anonymous Method: These can be termed as block of code being passed as an argument to a function call. A sort of inline function. For understanding this better, read my previous post on Anonymous Methods.

Background

We will start with understanding what we are trying to accompalish. We want a Generic List which facilitates the following:

  1. Sort on any property of the object in a collection, that too without writing any Custom Comparer. Once you finish reading David Hayden's blog where he talks about creating and using a Custom Comparer, you can understand the amount of code we write to facilitate sorting on custom properties. For example, we may have a collection of Person and would like to sort on "Lastname", "Firstname" and "Age".

    This requirement translates into providing a generic "OrderBy" clause/method into our custom generic list.

    So my version of David's code would proprably look like below:
    //Create Person collection
    LinqList<Person> people = new LinqList<Person>();
    people.Add(new Person("John","Doe", 76));
    people.Add(new Person("Abby","Normal", 25));
    people.Add(new Person("Jane","Doe", 84));
    
    //Sort by Lastname
    DisplayPeople(people.OrderBy<string>("Lastname"));
    
    //Sort by Age
    DisplayPeople(people.OrderBy<int>("Age"));
    
    //Sort by Firstname
    DisplayPeople(people.OrderBy<string>("Firstname"));
  2. Search/Find on any property of the object in a collection. Believe me or not, this too also requires us to write custom comparers; like ones which we saw on David Hayden's blog. For example, we may have a collection of Person and would like to search for Lastname="Doe" or Age=25.

    This requirement translates into providing a generic "Where" clause/method into our custom generic list.

    My code would proprably look like below:

    //Create Person collection
    LinqList<Person> people = new LinqList<Person>();
    people.Add(new Person("John","Doe", 76));
    people.Add(new Person("Abby","Normal", 25));
    people.Add(new Person("Jane","Doe", 84));
    
    //Search where Lastname = 'Doe'
    DisplayPeople(people.Where<string>("Lastname", "Doe"));
    
    //Search where Age = 25
    DisplayPeople(people.Where<int>("Age", 25));

I will call this generic list as "LinqList" because I am trying to imitate LINQ functionality with best of my ability which will possess an ability of performing search using "Where" and sort using "OrderBy".

So, How do we design Linq kind-of functionality?

Since, I am going to design this; it becomes my responsibility to help you map these with C# provided functionality. "Where" maps to GenericList.FindAll() and "OrderBy" to GenericList.Sort(). You must be thinking if this already exists than why are we trying to re-invent the wheel ?

I swear I am not cheating on you.. C# always looks for custom concrete comparer to perform the both - Find()/FindAll() and Sort(). To facilitate with a Generic Internal Comparer, I will be utilizing what we have learned from our past stories:

  1. Predicate Delegates
  2. Anonymous Methods
  3. Generic Methods and Generic Collections

Basic design consists of two components;

1) Finder - to facilitate GenericFind() and GenericSort()

Finder

2) LinqList - to consume Finder and turn a normal generic list into LINQ like list.

LinqList

Understanding the design of Finder

Let me show you the code of Finder.GenericFind()

/// <summary>
/// Perform find() operation
/// </summary>
/// <param name="collectionToBeSearched">Give the list to be searched</param>
/// <param name="criteria">Criteria to be compared</param>
/// <param name="fieldName">Name of the property to be compared</param>
/// <param name="finderOption">Types of Search Options to be used with Finder</param>
public List<listType> GenericFind(List<listType> collectionToBeSearched, fieldType criteria, string propertyName, SearchOptions finderOption)
{
    List<listType> findResults = collectionToBeSearched.FindAll(
        delegate(listType input)
        {
            bool result = false;

            fieldType currentValue = (fieldType)input.GetType().GetProperty(propertyName).GetValue(input, null);
            result = Compare<fieldType>(currentValue, criteria, finderOption);

            return result;
        });

    return findResults;
}
/// <summary>
/// Compare object of type IComparable using CompareTo()
/// T defines the type of objects to be compared
/// </summary>
private bool Compare<T>(T value, T criteria, SearchOptions finderOption) where T : IComparable<T>
{
    bool result = false;

    switch (finderOption)
    {
        case SearchOptions.GreaterThan:
            result = (value.CompareTo(criteria) > 0);
            break;
        case SearchOptions.GreaterThanOrEqualTo:
            result = (value.CompareTo(criteria) >= 0);
            break;
        case SearchOptions.LessThan:
            result = (value.CompareTo(criteria) < 0);
            break;
        case SearchOptions.LessThanEqualTo:
            result = (value.CompareTo(criteria) <= 0);
            break;
        case SearchOptions.EqualTo:
            result = (value.CompareTo(criteria) == 0);
            break;
        case SearchOptions.NotEqualTo:
            result = (value.CompareTo(criteria) != 0);
            break;
    }

    return result;
}

In GenericFind() method, I check the criteria using Predicate Delegate via Anonymous Method and then Concrete Reflection to dynamically read the value of the specified property and then evaluate the "read property value" to the criteria using Genrics based IComparable<T>.

There is another method too, Finder.GenericSort(); time to see the code :)

/// <summary>
/// Perform sort() operation
/// </summary>
/// <param name="collectionToBeSearched">Give the list to be searched</param>
/// <param name="criteria">Criteria to be compared</param>
/// <param name="fieldName">Name of the property to be compared</param>
/// <param name="sortOption">Types of Sort Options to be used with Finder</param>
public List<listType> GenericSort(List<listType> collectionToBeSearched, string propertyName, SortOptions sortOption)
{
    collectionToBeSearched.Sort(
        delegate(listType c1, listType c2)
        {
            int result;

            fieldType valueToCompareWith = (fieldType)c1.GetType().GetProperty(propertyName).GetValue(c1, null);
            fieldType valueToBeCompared = (fieldType)c2.GetType().GetProperty(propertyName).GetValue(c2, null);

            if (sortOption == SortOptions.Ascending)
                result = Comparer<fieldType>.Default.Compare(valueToCompareWith, valueToBeCompared);
            else
                result = Comparer<fieldType>.Default.Compare(valueToBeCompared, valueToCompareWith);

            return result;
        });

    return collectionToBeSearched;
}

In GenericSort() I used Anonymous Method to consume List<T>:Sort Method (IComparer<T>) and then Concrete Reflection to dynamically read the value of the specified property and then compare "specified property's value" using Generics based IComparer<T>.

To supplement the GenericFind() and GenericSort(), I used enums - SearchOptions and SortOptions. I will show you its self explanatory code:

/// <summary>
/// Types of Search Options to be used with Finder
/// </summary>
public enum SearchOptions
{
    GreaterThan,
    GreaterThanOrEqualTo,
    LessThan,
    LessThanEqualTo,
    EqualTo,
    NotEqualTo
}

/// <summary>
/// Types of Sort Options to be used with Finder
/// </summary>
public enum SortOptions
{
    Ascending,
    Descending
}

Understanding the design of LinqList

LinqList has a pretty simple code base. I just inherit it from Generic List<T> and add two generic based methods - Where and OrderBy; which internally consumes Finder.GenericFind() and Finder.GenericSort(), respectively.

/// <summary>
/// Use this class enrich your collection
/// with LINQ type functionality
/// </summary>
/// <typeparam name="T"></typeparam>
public class LinqList<T> : List<T>
{
    /// <summary>
    /// Search within your collection
    /// </summary>
    public LinqList<T> Where<R>(string propertyName, R criteria, SearchOptions findOption) where R : IComparable<R>
    {
        return new FinderUsingLinq<T, R>().GenericFind(this, criteria, propertyName, findOption);
    }

    /// <summary>
    /// Search within your collection
    /// using default EqualTo SearchOption
    /// </summary>
    public LinqList<T> Where<R>(string propertyName, R criteria) where R : IComparable<R>
    {
        return Where<R>(propertyName, criteria, SearchOptions.EqualTo);
    }

    /// <summary>
    /// Sort your collection
    /// </summary>
    public LinqList<T> OrderBy<R>(string propertyName, SortOptions sortOption) where R : IComparable<R>
    {
        //Perform the generic sort
        new FinderUsingLinq<T, R>().GenericSort(this, propertyName, sortOption);

        return this;
    }

    /// <summary>
    /// Sort your collection
    /// using default Ascending SortOption
    /// </summary>
    /// <typeparam name="R"></typeparam>
    /// <param name="propertyName"></param>
    /// <returns></returns>
    public LinqList<T> OrderBy<R>(string propertyName) where R : IComparable<R>
    {
        return OrderBy<R>(propertyName, SortOptions.Ascending);
    }
}

Let's use newly created LinqList to Search and Sort

I will use David's person class.

public class Person
    {
        #region Private Members

        private string _firstname;
        private string _lastname;
        private int _age;

        #endregion

        #region Properties

        public string Firstname
        {
            get { return _firstname; }
            set { _firstname = value; }
        }

        public string Lastname
        {
            get { return _lastname; }
            set { _lastname = value; }
        }

        public int Age
        {
            get { return _age; }
            set { _age = value; }
        }

        #endregion

        #region Contructors

        public Person(string firstname, string lastname, int age)
        {
            _firstname = firstname;
            _lastname = lastname;
            _age = age;
        }

        #endregion
    }

Interested in code for Search and Sort functionality?

    //Create Person collection
    LinqList<Person> people = new LinqList<Person>();
    people.Add(new Person("John","Doe", 76));
    people.Add(new Person("Abby","Normal", 25));
    people.Add(new Person("Jane","Doe", 84));
    
    //Sort by Lastname
    DisplayPeople(people.OrderBy<string>("Lastname"));
    //Sort by Age
    DisplayPeople(people.OrderBy<int>("Age"));
    //Sort by Firstname
    DisplayPeople(people.OrderBy<string>("Firstname"));
    //Search where Lastname = 'Doe'
    DisplayPeople(people.Where<string>("Lastname", "Doe"));
    //Search where Age = 25
    DisplayPeople(people.Where<int>("Age", 25));
    
    //Search where Lastname = 'Doe' and sort the resultset by Age
    DisplayPeople(people.Where<string>("Lastname", "Doe").OrderBy<int>("Age"));
    

Wondering about the DisplayPeople(); see it here

//Iterate through the collection and print person details
private static void DisplayPeople(LinqList<Person> people)
{
    Console.WriteLine("******** start ***********");

    //Print people list
    people.ForEach(delegate(Person person)
    {
        Console.WriteLine("{0} {1}, Age = {2}", person.Lastname, person.Firstname, person.Age);
    }
    );

    Console.WriteLine("******** end *************");
    Console.WriteLine();
}

Last but not the least :)

In this post, I explained how we can use some really cool features of C# 2.0 Framework and accompalish real simple and widely used functionality like "Search" and "Sort". If you read the syntax of Where and OrderBy, you can easily feel the power of flow based programming. Example:

//Search where Lastname = 'Doe' and sort the resultset by AgeDisplayPeople(people.Where<string>("Lastname", "Doe").OrderBy<int>("Age"));

TIP: I am not sure how many of my readers noticed DisplayPeople(). This method uses one of another C# feature - List<T>.ForEach(), which uses Action(T) delegate. For knowing more about the Action(T) delegate, read my other post - Lights .. Camera .. Action (Action Delegate).

For more details and updated Article refer to my post at http://www.domaindrivendesign.info/2008/01/using-predicate-delegate-anonymous.html

If you are an aspiring architect and wish to learn more about how we can design better solutions, you can read my blog at http://www.domaindrivendesign.info

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here