views:

529

answers:

6

The purpose of this is to avoid writing a ton of if() statements.

Here is my current code:

public override List<oAccountSearchResults> SearchForAccounts(oAccountSearchCriteria searchOptions)
{
    List<oAccountSearchResults> results = Service.SearchForAccounts(searchOptions);
    results.Sort((a1, a2) => a2.AccountNumber.CompareTo(a1.AccountNumber));
    return results;
}

What I would like to do is provide a parameter which tells me which field to sort on. Then dynamically update my sort criteria without having a bunch of if() statements such as this:

public override List<oAccountSearchResults> SearchForAccounts(oAccountSearchCriteria searchOptions, string sortCriteria)
{
    List<oAccountSearchResults> results = Service.SearchForAccounts(searchOptions);
    if (sortCriteria == "AccountNumber")
    {
     results.Sort((a1, a2) => a2.AccountNumber.CompareTo(a1.AccountNumber));
    }
    else if (sortCriteria == "FirstName")
    {
     results.Sort((a1, a2) => a2.FirstName.CompareTo(a1.FirstName));
    }
    return results;
}

I would like to do this without having about 30 if() statements for all the sortable criteria that will be available.

Any and all help will be appreciated.

EDIT WITH SOLUTION:

Thank you all for your responses.

David, your approached worked but I think that Richard's answer works a bit better.

Here is the ultimate solution that I came up with. I used David's framework for the example and Richards implementation:

using System;
using System.Collections.Generic;

namespace SortTest
{
    class Program
    {
        static void Main(string[] args)
        {


            var results1 = Search(oObject => oObject.Value1);

            foreach (oObject o in results1)
            {
                Console.WriteLine(o.Value1 + ", " + o.Value2);
            }
            Console.WriteLine(Environment.NewLine);
            var results2 = Search(oObject => oObject.Value2);

            foreach (oObject o in results2)
            {
                Console.WriteLine(o.Value1 + ", " + o.Value2);
            }


            Console.ReadLine();
        }

        public static List<oObject> Search<T>(Func<oObject, T> keyExtract) where T: IComparable 
        {
            var results = new List<oObject>
                                            {
                                                new oObject {Value1 = "A 1", Value2 = "B 2"},
                                                new oObject {Value1 = "B 1", Value2 = "A 2"}
                                            };

            results.Sort((a, b) => keyExtract(a).CompareTo(keyExtract(b)));
            return results;
        }
    }       
    class oObject
    {
        public string Value1 { get; set; }
        public string Value2 { get; set; }
    }
}
A: 

Have you considered simply returning an IEnumerable and allowing the cosumer to determine how they would like to sort it there.

Ayende Rahien makes a compelling case for this sort of behavior here: http://ayende.com/Blog/archive/2009/04/18/the-dal-should-go-all-the-way-to-ui.aspx

Josh
+3  A: 

If the caller could supply an expression which extracts the value to use to compare, you can call that delegate in the comparison function:

public override List<oAccountSearchResults> SearchForAccounts<T>(
              oAccountSearchCriteria searchOptions,
              Func<oAccountSearchResults, T> keyExtract) where T : IComparable {
  List<oAccountSearchResults> results = Service.SearchForAccounts(searchOptions);

  results.Sort(a,b) => keyExtract(a).CompareTo(keyExtract(b)));
  return results;
}
Richard
You want to add a constraint there that T : IComparable<T>, or use Comparer<T>.Default.Compare(...)
Jon Skeet
I am having difficulties making this work. results.Sort((a , b) => keyExtract(a).CompareTo(keyExtract(b)));It is giving me cannot resolve symbole "CompareTo" Thoughts?
Jason Heine
@Jon: Of course. fixed. @Jason: addition of generic constraint should fix that.
Richard
This looks like it would work fine when using CompareTo for string values however what if @Jason needed to do an orderby on an object. Where the compareto would look at the hash value of the object and the compareto would return false because the hash would be different. Just a thought
David Yancey
@David: The types need to implement IComparable... which is the general requirement for sorting. Another approach would be an overload which returns without sort, the result can then be sorted by whatever means the caller wishes to use. If this were a library I would also look at an overload that took a Func<oAccountSearchResults,oAccountSearchResults,int> to use as a comparison function.
Richard
@Richard: I like the flexability of the overload without sort. Thanks for the info. +1
David Yancey
Thank you for the great code!
Jason Heine
+1  A: 

Hi, what about this:

public override List<oAccountSearchResults> SearchForAccounts(oAccountSearchCriteria searchOptions, Comparsion<oAccountSearchResults> sortCriteria)
{
    List<oAccountSearchResults> results = Service.SearchForAccounts(searchOptions);

    results.Sort(sortCriteria);

    return results;
}

then you use it like:

SearchForAccounts(searchOptionsObject, (x,y) => x.Property.CompareTo(y.Property));
Jan Remunda
That would result in a lot of duplication in calling code.
Leyu
+1  A: 

Use a map from String to Comparer<oAccountSearchResults> so you can work out what the sort criteria means in code terms from the string. Then you can just call Sort in the normal way.

private static readonly Dictionary<String,Comparer<oAccountSearchResults>>
    SortOrders = new Dictionary<String,Comparer<oAccountSearchResults>>
{
    { "AccountNumber", (a1, a2) => a2.AccountNumber.CompareTo(a1.AccountNumber) },
    { "FirstName", (a1, a2) => a2.FirstName.CompareTo(a1.FirstName) }
    // etc
};

public override List<oAccountSearchResults> SearchForAccounts(
    oAccountSearchCriteria searchOptions, string sortCriteria)
{
    Comparer< oAccountSearchCriteria> sortOrder;
    if (!SortOrders.TryGetValue(sortCriteria, out sortOrder))
    {
        throw new ArgumentException("Unknown sort order " + sortCriteria);
    }
    List<oAccountSearchResults> results = Service.SearchForAccounts(searchOptions);
    results.Sort(sortOrder);
    return results;
}
Jon Skeet
A: 

Do what the .Net framework developers did and let the caller tell you how to sort.

http://msdn.microsoft.com/en-us/library/bb534966.aspx

David B
Yes! Don't lock yourself into having to modify your sort code for every whim of the client.
Josh
+1  A: 

You can try it like this. I have created a sample object for testing purposes:

You can view the original source from here but cleaned up for readability purposes:

http://msdn.microsoft.com/en-us/library/bb534966.aspx

First create an extension method on IEnumerable:

public static class EnumerableExtension

    {

        public static IOrderedEnumerable<T> OrderBy<T>(this IEnumerable<T> items, string property, bool ascending)

        {

            var myObject = Expression.Parameter(typeof (T), "MyObject");



            var myEnumeratedObject = Expression.Parameter(typeof (IEnumerable<T>), "MyEnumeratedObject");



            var myProperty = Expression.Property(myObject, property);



            var myLambda = Expression.Lambda(myProperty, myObject);



            var myMethod = Expression.Call(typeof (Enumerable), ascending ? "OrderBy" : "OrderByDescending",

                                           new[] {typeof (T), myLambda.Body.Type}, myEnumeratedObject, myLambda);



            var mySortedLambda =

                Expression.Lambda<Func<IEnumerable<T>, IOrderedEnumerable<T>>>(myMethod, myEnumeratedObject).Compile();



            return mySortedLambda(items);

        }

    }

Here is our test object:

class oObject

{

    public string Value1 { get; set; }

    public string Value2 { get; set; }



}

Then in your program you can do this:

static void Main(string[] args)

        {

            var results = new List<oObject>

                                            {

                                                new oObject {Value1 = "A", Value2 = "B"},

                                                new oObject {Value1 = "B", Value2 = "A"}

                                            };



            IEnumerable<oObject> query = results.OrderBy("Value2", false);

            foreach (oObject o in query)

            {

                Console.WriteLine(o.Value1 + ", " + o.Value2);

            }

            Console.WriteLine(Environment.NewLine);

            IEnumerable<oObject> query2 = results.OrderBy("Value1", false);

            foreach (oObject o in query2)

            {

                Console.WriteLine(o.Value1 + ", " + o.Value2);

            }

            Console.ReadLine();

        }

Your results will be:

Query 1:

A, B

B, A

Query 2:

B, A

A, B

David Yancey
I am going to give this a try and see if it works. Thanks
Jason Heine
While this worked, I like Richards a bit better. Thanks a bunch!
Jason Heine