Click here to Skip to main content
15,885,985 members
Articles / Desktop Programming / Windows Forms

Crystal Image Toolkit: thumbnail image control and picture viewing.

Rate me:
Please Sign up or sign in to vote.
4.68/5 (21 votes)
11 May 2011LGPL37 min read 109.6K   8.3K   95  
Thumbnail and image viewing controls for Windows Forms, using C#.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace Phydeaux.Utilities
{
    public sealed class DynamicComparer<T> : IComparer<T>
    {
        #region Private Fields
        private DynamicMethod method;
        private Comparison<T> comparer;
        #endregion

        #region Properties
        public Comparison<T> Comparer
        {
			get
			{
				return comparer;
			}
        }
        #endregion

        #region Constructors
        public DynamicComparer(string orderBy)
        {
            Initialize(orderBy);
        }

        public DynamicComparer(SortProperty[] sortProperties)
        {
            Initialize(sortProperties);
        }
        #endregion

        #region Public Methods
        public void Initialize(string orderBy)
        {
            Initialize(SortProperty.ParseOrderBy(orderBy));
        }

        public void Initialize(SortProperty[] sortProperties)
        {
            SortProperty.BindSortProperties(sortProperties, typeof(T));
            method = CreateDynamicCompareMethod(sortProperties);
            comparer = (Comparison<T>)method.CreateDelegate(typeof(Comparison<T>));
        }

        #region IComparer<T> Members
        public int Compare(T x, T y)
        {
            return comparer.Invoke(x, y);
        }
        #endregion
        #endregion

        #region Private Methods
        private DynamicMethod CreateDynamicCompareMethod(SortProperty[] sortProperties)
        {
            // at this time, the inner loop is (worst case) 39 IL bytes per property with short branches
            const int BytesPerProperty = 39;
            const int PropertiesPerShortBranch = 128 / BytesPerProperty;

            DynamicMethod dm = new DynamicMethod("DynamicCompare"
                , MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard,
                typeof(int), new Type[] { typeof(T), typeof(T) }, typeof(T), false);
            dm.InitLocals = false;
            DynamicEmit de = new DynamicEmit(dm);

            #region Generate IL for dynamic method
            Dictionary<Type, LocalBuilder> localVariables = new Dictionary<Type, LocalBuilder>();
            bool isValueType = typeof(T).IsValueType;

            if (sortProperties.Length > 0)
            {
                Label breakLabel = de.DefineLabel();

                // For each of the properties we want to check inject the following.
                int numberLeft = sortProperties.Length;
                foreach (SortProperty property in sortProperties)
                {
                    Label continueLabel = de.DefineLabel();
                    Type propertyType = property.ValueType;

                    // Load argument at position 0.
                    de.Get(isValueType, 0, property);

                    // If the type is an Enum, then we need to box it...
                    if (propertyType.IsEnum)
                    {
                        de.BoxIfNeeded(propertyType);
                    }
                    else if (propertyType.IsValueType)
                    {
                        if (!property.IsNullable)
                        {
                            // If the type is a ValueType then we need to inject code to store
                            // it in a local variable, this insures it doesn't get boxed.
                            // Do we have a local variable for this type?
                            LocalBuilder localBuilder;

                            if (!localVariables.TryGetValue(propertyType, out localBuilder))
                            {
                                // Adds a local variable of type x and remember it
                                localBuilder = de.DeclareLocal(propertyType);
                                localVariables.Add(propertyType, localBuilder);
                            }

                            // This local variable is for handling value types of type x.
                            int localIndex = localBuilder.LocalIndex;

                            de.StoreLocal(localIndex);       // Store the value in the local var at position x.
                            de.LoadLocalAddress(localIndex); // Load the address of the local
                        }
                    }
                    else
                    {
                        // value is an reference type
                        Label leftNotNull = de.DefineLabel();
                        Label rightNotNull = de.DefineLabel();
                        de.Duplicate(); // left is now on stack twice.
                        de.BranchIfNotNull(leftNotNull, true);

                        // Left is null
                        de.Pop(); // discard second copy of left

                        // Get right...
                        de.Get(isValueType, 1, property);

                        // and check if right is not null
                        de.BranchIfNotNull(rightNotNull, true);

                        // We know that right is null too, thus they are equal
                        de.LoadLiteral(0);
                        de.Branch(continueLabel, numberLeft <= PropertiesPerShortBranch);

                        // Okay, right is NOT null, left is less
                        de.MarkLabel(rightNotNull);
                        de.LoadLiteral(-1);
                        de.Branch(continueLabel, numberLeft <= PropertiesPerShortBranch);
                        
                        de.MarkLabel(leftNotNull);
                    }

                    // Load argument at position 1.
                    de.Get(isValueType, 1, property);

                    // If the type is an Enum, then we need to box it...
                    if (propertyType.IsEnum)
                    {
                        de.BoxIfNeeded(propertyType);
                    }

                    // Compare the top 2 items in the evaluation stack and push the return value onto the stack.
                    if (property.IsNullable)
                    {
                        // use Nullable's Compare method
                        MethodInfo elementCompare = typeof(Nullable).GetMethod("Compare");
                        elementCompare = elementCompare.MakeGenericMethod(propertyType.GetGenericArguments()[0]);
                        de.Call(elementCompare);
                    }
                    else
                    {
                        //Comparer<T>.Default;
                        // use propertyType's CompareTo method
                        MethodInfo elementCompare = propertyType.GetMethod("CompareTo", new Type[] { propertyType });
                        de.Call(elementCompare);
                    }

                    de.MarkLabel(continueLabel);

                    // If the sort should be descending we need to flip the result of the comparison.
                    if (property.Descending)
                        de.Negate();

                    if (--numberLeft > 0)
                    {
                        de.Duplicate();     // save a copy of the return value
                        // Is the result is not zero, we're done so break out of the loop.
                        de.BranchIfNonZero(breakLabel, numberLeft <= PropertiesPerShortBranch); 
                        de.Pop();           // discard the (known 0) copy of the return value
                    }
                }

                de.MarkLabel(breakLabel); // This is the spot where the label we created earlier should be added.
            }
            else
            {
                // if there are no properties, call object comparer directly...
                de.LoadArgument(isValueType, 0);    // Load argument at position 0.
                de.LoadArgument(1);                 // Load argument at position 1.
                MethodInfo instanceCompare = typeof(T).GetMethod("CompareTo", new Type[] { typeof(T) });
                de.Call(instanceCompare);
            }

            de.Return(); // Return the value.
            #endregion

            return dm;
        }
        #endregion
    }

    /// <summary>
    /// Internal struct to carry the sorting properties.
    /// </summary>
    public struct SortProperty
    {
        #region Properties
        private string name;
        public string Name
        {
			get
			{
				return name;
			}
			set
			{
				ArgumentValidation.EmptyCheck(value, "value");
				name = value.Trim();
			}
        }

        private bool descending;
        public bool Descending
        {
			get
			{
				return descending;
			}
			set
			{
				descending = value;
			}
		}

        public static bool IsComparable(Type valueType)
        {
            bool isNullable;
            return IsComparable(valueType, out isNullable);
        }
        
        public static bool IsComparable(Type valueType, out bool isNullable)
        {
            isNullable = valueType.IsGenericType
                    && ! valueType.IsGenericTypeDefinition
                    && valueType.IsAssignableFrom(typeof(Nullable<>).MakeGenericType(valueType.GetGenericArguments()[0]));

            return (typeof(IComparable).IsAssignableFrom(valueType)
                    || typeof(IComparable<>).MakeGenericType(valueType).IsAssignableFrom(valueType)
                    || isNullable);
        }

        #region Internals
        private Type valueType;
        internal Type ValueType
        {
			get
			{
				return valueType;
			}
            private set
            {
                valueType = value;

                if (!IsComparable(value, out isNullable))
                {
                    throw new NotSupportedException("The type \""
                        + value.FullName
                        + "\" of the property \""
                        + this.Name
                        + "\" does not implement IComparable, IComparible<T> or is Nullable<T>.");
                }
            }
        }

        private MethodInfo get;
        internal MethodInfo Get
        {
			get
			{
				return get;
			}
			private set
			{
				get = value;
			}
		}

        private FieldInfo field;
        internal FieldInfo Field
        {
			get
			{
				return field;
			}
			private set
			{
				field = value;
			}
		}

        private bool isNullable;
        internal bool IsNullable
		{
			get
			{
				return isNullable;
			}
		}
        #endregion
        #endregion

        #region Static methods
        public static SortProperty[] ParseOrderBy(string orderBy)
        {
			ArgumentValidation.NullCheck(orderBy, "orderBy");

            string[] properties = orderBy.Split(new char[] { ',' } , StringSplitOptions.RemoveEmptyEntries);
            SortProperty[] sortProperties = new SortProperty[properties.Length];

            for (int i = 0; i < properties.Length; i++)
            {
                bool descending = false;
                string property = properties[i].Trim();
                string[] propertyElements = property.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                if (propertyElements.Length > 1)
                {
                    if (propertyElements[1].Equals("DESC", StringComparison.OrdinalIgnoreCase))
                    {
                        descending = true;
                    }
                    else if (propertyElements[1].Equals("ASC", StringComparison.OrdinalIgnoreCase))
                    {
                        // already set to descending = false;
                    }
                    else
                    {
                        throw new ArgumentException("Unexpected sort order type \"" + propertyElements[1] + "\" for \"" + propertyElements[0] + "\"", "orderBy");
                    }
                }

                sortProperties[i] = new SortProperty(propertyElements[0], descending);
            }

            return sortProperties;
        }
        #endregion

        #region Constructors
        public SortProperty(string propertyName, bool sortDescending)
        {
			ArgumentValidation.EmptyCheck(propertyName, "propertyName");
            name = propertyName;
            descending = sortDescending;

            // we set these when accessor validated
            valueType = null;
            get = null;
            field = null;
            isNullable = false;
        }
        #endregion

        #region Internals
        internal static void BindSortProperties(SortProperty[] sortProperties, Type instanceType)
        {
            BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

            if (sortProperties == null)
                sortProperties = new SortProperty[0];

            if (sortProperties.Length > 0)
            {
                for (int index = 0; index < sortProperties.Length; index++)
                {
                    string propertyName = sortProperties[index].Name;
                    PropertyInfo propertyInfo = instanceType.GetProperty(propertyName, BindingFlags.GetProperty | flags);

                    if (propertyInfo != null)
                    {
                        sortProperties[index].ValueType = propertyInfo.PropertyType;
                        sortProperties[index].Get = propertyInfo.GetGetMethod(true);
                    }
                    else
                    {
                        FieldInfo fieldInfo = instanceType.GetField(propertyName, BindingFlags.GetField | flags);

                        if (fieldInfo != null)
                        {
                            sortProperties[index].ValueType = fieldInfo.FieldType;
                            sortProperties[index].Field = fieldInfo;
                        }
                        else
                        {
                            throw new ArgumentException("No public property or field named \""
                                + propertyName
                                + "\" was found in type \""
                                + instanceType.FullName
                                + "\".");
                        }
                    }
                }
            }
            else
            {
                if (!IsComparable(instanceType))
                    throw new NotSupportedException("The type \""
                        + instanceType.FullName
                        + "\" does not implement IComparable, IComparable<T> nor is a Nullable<T>.");
            }
        }
        #endregion
    }
}

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 GNU Lesser General Public License (LGPLv3)


Written By
Software Developer (Senior)
United States United States
Richard has been working with Windows software since 1991. He has worked for Borland, Microsoft, Oracle, and various startup companies such as Livescribe. Currently he is developing projects in C#, Windows Forms, and Net Framework. Visit his blog Attilan (www.attilan.com) to learn more about his tools, projects and discoveries.

Comments and Discussions