Click here to Skip to main content
12,502,104 members (56,345 online)
Click here to Skip to main content

Stats

49.8K views
387 downloads
71 bookmarked
Posted

Generic Multi-Field/Property Sorting for Lists of Business Objects

, 13 Feb 2008 CPOL
This article presents a simple and flexible way to sort strongly-typed lists of business objects using multiple properties or fields.
MultiSortLib
bin
Debug
MultiSortLib.dll
Release
MultiSortLib.instr.pdb
Properties
HyperPropertyDescriptor
HyperPropertyDescriptor
bin
Release
HyperPropertyDescriptor.csproj.user
Properties
HyperPropertyDescriptorSample
bin
Release
HyperPropertyDescriptor.dll
HyperPropertyDescriptor.pdb
HyperPropertyDescriptorSample.exe
HyperPropertyDescriptorSample.pdb
HyperPropertyDescriptorSample.csproj.user
Properties
MultiSortDemo
bin
Debug
MultiSortDemo.exe
MultiSortDemo.vshost.exe
MultiSortLib.dll
Release
MultiSortDemo.instr.pdb
MultiSortDemo.vshost.exe
MultiSortLib.instr.pdb
Properties
DataSources
WorkItem.datasource
WorkItemPropertyName.datasource
using System;
using Hyper.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Security.Permissions;

/* Change history:
 * 20 Apr 2007  Marc Gravell    Improved timing data; control META_CYCLES separately;
 *                              added GC.Collect between tests (since large object count);
 *                              tested without ReflectionPermssion (thank/credit to Josh Smith)
 * 
 * 
 */
namespace HyperPropertyDescriptorSample {
    public sealed class MyEntityNamePropertyDescriptor : ChainingPropertyDescriptor {
        public MyEntityNamePropertyDescriptor(PropertyDescriptor parent) : base(parent) { }
        public override object GetValue(object component) {
            return (string) ((MyEntity)component).Name;
        }
        public override void SetValue(object component, object value) {
            ((MyEntity)component).Name = (string)value;
        }
        public override bool IsReadOnly {
            get { return false; }
        }
        public override bool SupportsChangeEvents {
            get { return true; }
        }
        public override void AddValueChanged(object component, EventHandler handler) {
            ((MyEntity)component).NameChanged += handler;
        }
        public override void RemoveValueChanged(object component, EventHandler handler) {
            ((MyEntity)component).NameChanged -= handler;
        }        
    }
    
    public class MySuperEntity : MyEntity {
        private DateTime when;
        public DateTime When {
            get {
                opCount++;
                return when;
            }
            set {
                opCount++;
                when = value;
            }
        }
    }
    
    public class MyEntity {
        // opCount is a marker of work done; make protected (as an exception)
        // to minimise impact
        protected int opCount;
        private string name;
        private EventHandler _nameChanged;
        public event EventHandler NameChanged {
            add { opCount++; _nameChanged += value; }
            remove{ opCount++; _nameChanged -= value; }
        }
        public string Name {
            get {
                opCount++;
                return name;
            }
            set {
                opCount++;
                if (value != Name) {
                    name = value;
                    EventHandler handler = _nameChanged;
                    if (handler != null) handler(this, EventArgs.Empty);
                }
            }
        }
        public int OpCount { get { return opCount; } }
    }
    static class Program {
        /// <summary>
        /// The number of standard operations (GetValue(), etc) to perform
        /// </summary>
        const int CYCLES = 5000000;
        /// <summary>
        /// The number of times to invoke GetProperties (slower)
        /// </summary>
        const int META_CYCLES = 100000;

        static void Main() {
            // verify that things work OK without reflection access
            new ReflectionPermission(ReflectionPermissionFlag.AllFlags).Deny();

            Console.WriteLine("Direct access");
            TestDirect(1, false); // for JIT etc
            TestDirect(CYCLES, true);

            Console.WriteLine();
            string typeName = typeof(HyperTypeDescriptionProvider).Name;
            Console.WriteLine("Without " + typeName);

            RunTests(1, 1, false); // for JIT etc
            RunTests(META_CYCLES, CYCLES, true);
            
            HyperTypeDescriptionProvider.Add(typeof(MyEntity));
            Console.WriteLine();
            Console.WriteLine("With " + typeName);

            RunTests(1, 1, false); // for Emit, JIT etc
            RunTests(META_CYCLES, CYCLES, true);
        }
        static void RunTests(int metaCount, int count, bool report) {
            // note: GC.Collect here in timing mode (report==true) to
            // minimise chance of increasing object count triggering GC
            // for later tests. Not recommended for production code, but
            // acceptable for levelling the field in performance tests.
            if (report) { GC.Collect(); }
            Test<MyEntity>(metaCount, count, "Name", report);
            if (report) { GC.Collect(); }
            Test<MySuperEntity>(metaCount, count, "Name", report);
            if (report) { GC.Collect(); }
            Test<MySuperEntity>(metaCount, count, "When", report);
            if (report) { GC.Collect(); }
        }
        static void TestDirect(int count, bool output) {
            // initialise
            MyEntity t = new MyEntity();
            string value = "";

            // GetValue
            Stopwatch getValue = new Stopwatch();
            getValue.Start();
            DateTime startGetValue = DateTime.Now;
            for (int i = 0; i < count; i++) {
                value = t.Name;
            }
            getValue.Stop();

            // SetValue
            Stopwatch setValue = new Stopwatch();
            setValue.Start();
            for (int i = 0; i < count; i++) {
                t.Name = value;
            }
            setValue.Stop();

            // ValueChanged
            Stopwatch valueChanged = new Stopwatch();
            valueChanged.Start();
            EventHandler handler = ValueChanged;
            for (int i = 0; i < count; i++) {
                t.NameChanged += handler;
                t.NameChanged -= handler;
            }
            valueChanged.Stop();

            if (output) {
                Report<MyEntity>("Name", "GetValue", getValue);
                Report<MyEntity>("Name", "SetValue", setValue);
                Report<MyEntity>("Name", "ValueChanged", valueChanged);
                Console.WriteLine("OpCount: " + t.OpCount);
            }
        }
        static void Test<T>(int metaCount, int count, string name, bool output) where T : MyEntity, new() {
            // initialise
            T t = new T();
            PropertyDescriptorCollection props = null;
            PropertyDescriptor property = null;
            object value = null;
            bool isReadOnly = true, supportsChangeEvents = false;

            // GetProperties
            Stopwatch getProperties = new Stopwatch();
            getProperties.Start();
            for (int i = 0; i < metaCount; i++) {
                props = TypeDescriptor.GetProperties(t);
            }
            getProperties.Stop();
            if (props != null) property = props[name];
            
            // IsReadOnly
            Stopwatch isReadOnlyWatch = new Stopwatch();
            isReadOnlyWatch.Start();
            for(int i = 0; i < count; i++) {
                isReadOnly = property.IsReadOnly;
            }
            isReadOnlyWatch.Stop();

            // SupportsNotification
            Stopwatch supportsChangeEventsWatch = new Stopwatch();
            supportsChangeEventsWatch.Start();
            for(int i = 0; i < count; i++) {
                supportsChangeEvents = property.SupportsChangeEvents;
            }
            supportsChangeEventsWatch.Stop();

            // GetValue
            Stopwatch getValue = new Stopwatch();
            getValue.Start();
            for(int i = 0; i < count; i++) {
                value = property.GetValue(t);
            }
            getValue.Stop();

            // SetValue
            Stopwatch setValue = new Stopwatch();
            if(!isReadOnly) {
                setValue.Start();
                for(int i = 0; i < count; i++) {
                    property.SetValue(t, value);
                }
                setValue.Stop();
            }
            
            // ValueChanged
            Stopwatch valueChanged = new Stopwatch();
            if(supportsChangeEvents) {
                EventHandler handler = ValueChanged;
                valueChanged.Start();
                for(int i = 0; i < count; i++) {
                    property.AddValueChanged(t, handler);
                    property.RemoveValueChanged(t, handler);
                }
                valueChanged.Stop();
            }
            
            if(output) {
                Report<T>(name, "GetProperties", getProperties);
                Report<T>(name, "IsReadOnly", isReadOnlyWatch);
                Report<T>(name, "SupportsChangeEvents", supportsChangeEventsWatch);
                Report<T>(name, "GetValue", getValue);
                if(!isReadOnly) {
                    Report<T>(name, "SetValue", setValue);
                }
                if(supportsChangeEvents) {
                    Report<T>(name, "ValueChanged", valueChanged);
                }
                Console.WriteLine("OpCount: " + t.OpCount);
            }
        }

        static void Report<T>(string propertyname, string test, Stopwatch watch)
        {
            int ms = (int) Math.Round(watch.Elapsed.TotalMilliseconds);
            Console.WriteLine("{0}.{1}\t{2}\t{3}ms", typeof(T).Name, propertyname, test, ms);
        }
        
        static void ValueChanged(object sender, EventArgs args) {}        
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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

Share

About the Author

owen654321
Software Developer (Senior) Troppus Software
United States United States
Currently working as a Senior Silverlight Developer with Troppus Software in Superior, CO. I enjoy statistics, programming, new technology, playing the cello, and reading codeproject articles. Smile | :)

You may also be interested in...

Pro
Pro
| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160919.1 | Last Updated 13 Feb 2008
Article Copyright 2007 by owen654321
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid