1

I have a need to sort a collection of these based upon criteria determined at run-time.

I was using the code from this article to perform the sorting - originally my code used the dynamic class.

Then I hit issues with serialization over WCF so I switched to using a SerializableDynamicObject and now the sorting code breaks on the line:

  PropertyInfo pi = type.GetProperty(prop);

with the error that SerializableDynamicObject does not have a property called "Name" - where "Name" was the value of prop.

I guess the simplest thing to do is to find an alternate way of serializing a dynamic type that the sorting algorithm works with. Any pointers in this direction would be appreciated!

I have looked at this example, but I get the error message:

The constructor with parameters (SerializationInfo, StreamingContext) is not found in ISerializable type
Community
  • 1
  • 1
BonyT
  • 10,750
  • 5
  • 31
  • 52

2 Answers2

2

Here's some code using FastMember for this, which works for both reflection-based and dynamic-based objects (depending on what you pass to TypeAccessor.Create)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using FastMember;

namespace ConsoleApplication6
{
    class Program
    {
        static void Main()
        {
            var list = new List<dynamic>();
            dynamic obj = new ExpandoObject();
            obj.Foo = 123;
            obj.Bar = "xyz";
            list.Add(obj);
            obj = new ExpandoObject();
            obj.Foo = 456;
            obj.Bar = "def";
            list.Add(obj);
            obj = new ExpandoObject();
            obj.Foo = 789;
            obj.Bar = "abc";
            list.Add(obj);

            var accessor = TypeAccessor.Create(
                typeof(IDynamicMetaObjectProvider));
            string propName = "Bar";
            list.Sort((x,y) => Comparer.Default.Compare(
                accessor[x, propName], accessor[y,propName]));

            foreach(var item in list) {
                Console.WriteLine(item.Bar);
            }
        }
    }
}

It may be worth mentioining that for reflection-based types, this does not use reflection on a per-item basis; all that is optimized away via meta-programming.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Brilliant - thanks for that Marc, and the helpful comment on your other post - I'd have spent ages trying to go in the wrong direction otherwise. – BonyT Jun 26 '12 at 14:06
0

Marc Gravell's answer gave me the answer to complete this - I needed to implement a sorter that could handle multiple sort criteria, not known until runtime. I'm accepting Marc's answer, but posting this as someone may find it useful too.

There may be a more elegant way of achieving this, if so please let me know and I'll update the answer.

 public class SerializableDynamicObjectComparer: IComparer
{
    private readonly List<KeyValuePair<string, bool>> sortCriteria = new List<KeyValuePair<string, bool>>();

    private readonly TypeAccessor accessor;

    public SerializableDynamicObjectComparer(IEnumerable<string> criteria)
    {
        foreach (var criterium in criteria)
        {
            string[]  sortCriterium = criterium.Split('.');

            this.sortCriteria.Add(new KeyValuePair<string, bool>(sortCriterium[0],
                                                                 sortCriterium.Length == 0
                                                                     ? sortCriterium[1].ToUpper() == "ASC"
                                                                     : false));
        }

        this.accessor = TypeAccessor.Create(typeof (IDynamicMetaObjectProvider));
    }

    public int Compare(object x, object y)
    {
        for(int i=0; i< this.sortCriteria.Count; i++)
        {
            string fieldName = this.sortCriteria[i].Key;
            bool isAscending = this.sortCriteria[i].Value;
            int result = Comparer.Default.Compare(this.accessor[x, fieldName], this.accessor[y, fieldName]);
            if(result != 0)
            {
                //If we are sorting DESC, then return the -ve of the default Compare result
                return isAscending ? result : -result;
            }
        }

        //if we get here, then objects are equal on all sort criteria.
        return 0;
    }
}

Usage:

var sorter = new SerializableDynamicObjectComparer(sortCriteria);

var sortableData = reportData.ToList();
sortableData.Sort(sorter.Compare);

where sortCriteria is an array of strings e.g.

 new {"Name.DESC", "Age.ASC", "Count"}
BonyT
  • 10,750
  • 5
  • 31
  • 52
  • My main concern with this would be the number of times you call `ToUpper`. Watch out for that! I would test this into a `bool` first. In fact, you want to do **almost nothing** in `Compare(x,y)` - that will get invoked ***lots***. Do it in the constructor instead. – Marc Gravell Jun 26 '12 at 14:10
  • Yes - very good point! - Will correct and update - thanks again! – BonyT Jun 26 '12 at 14:13