[C#] Удобная сортировка. Генератор Comparison<T>
От: Быстрое Гонзало  
Дата: 26.06.06 17:13
Оценка: 32 (4)
По набору названий полей или методов класс генерит динамический Comparison<T> используя который потом можно чего-нибудь отсортировать.
Встроено два метода, зовущие стандартные Array.Sort() и List<>.Sort();
Можно указывать поля вложенных объектов, главное чтоб то, по чему сортируем, реализовывало CompareTo(). (С nullable`ами не работает)
Ну в примере все видно.

Хранит все однажды сгенеренные методы в словаре.
Работает, конечно, медленнее чем не динамические методы, но не в 2а раза.
Особенно полезен, когда заранее не известны ни тип сортируемых объектов ни критерий.


Пользоваться так:


 public class Tst
    {
        public int    field1 = 1;
        public string field2 = "field2";
        public double prop1
        {            get { return (double)field1; }        }
        public DateTime method1()
        {            return DateTime.Now;        }
    }

Tst[] tst; //List<Tst> tst;

ILRSort.Sort(tst, "field1");
ILRSort.Sort(tst, "field2[desc]"); //descending
ILRSort.Sort(tst, "prop1, method1()[desc], field2"); 
            
ILRSort.Sort(tst, "prop1, method1().Ticks.ToString()[desc]");
ILRSort.Sort(tst, "prop1, prop1,prop1,prop1,prop1 "); // и т.д.
ILRSort.Sort(tst, "method1().ToShortDateString().GetHashCode().ToString().Length.ToString()");//и т.п
Re: [C#] Удобная сортировка. Генератор Comparison<T>
От: Быстрое Гонзало  
Дата: 26.06.06 17:14
Оценка:
Исходник

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace rBo3Du
{
    /// <summary>
    /// usage 
    /// ILRSort.Sort(client, "Name, SecondName[asc], LastName[desc], Other"); //сортировка по полям или пропертям в разные стороны
    /// ILRSort.Sort(client, "ToString()[desc], Name"); // по возвращаемому значению 
    /// короче по всякому
    /// </summary>
    public class ILRSort
    {
        public static void Sort<T>(T[] array, string by)
        {
            Array.Sort(array, JustGetThisComparison<T>(by));
        }
        public static void Sort<T>(List<T> list, string by)
        {
            list.Sort(JustGetThisComparison<T>(by));
        }
        public static Comparison<T> JustGetThisComparison<T>(string compBy)
        {
            Type t = typeof(T);
            Comparison<T> d = (Comparison<T>)GetDelegate(t, compBy.Replace(" ",""));
            return d;
        }

        /// <summary>
        /// Type - тип сортируемого объекта
        /// string - набор сортировок
        /// delegate - тот самый компаризон
        /// </summary>
        private static Dictionary<Type, Dictionary<string, Delegate>> comparisons = new Dictionary<Type,Dictionary<string,Delegate>>();
        private static long methods = 0;
        private static string nextComparisonMethodName
        {
            get
            {
                string methodName = "Comp_" + methods.ToString();
                methods++;
                return methodName;
            }
        }


        private static Delegate GetDelegate(Type t, string fieldName)
        {
            Dictionary<string, Delegate> fields = null;
            if (comparisons.ContainsKey(t))
            {
                fields = comparisons[t];
                if (fields.ContainsKey(fieldName))
                    return fields[fieldName];
            }
            if (fields == null) //такого типа нет в словаре
            {
                fields = new Dictionary<string, Delegate>();
                comparisons.Add(t, fields);
            }
            fields.Add(fieldName, ConstructDelegate(t, fieldName)); 
            return fields[fieldName];
        }
        private static Delegate ConstructDelegate(Type t, string fieldName)
        {
            string[] differs = fieldName.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            ComparisonData[] data = new ComparisonData[differs.Length];
            for (int i = 0; i < differs.Length; i++)
            {
                data[i] = ComparisonData.ConstructSection(differs[i],t);

                if (data[i].GetEndCompareTo() == null)
                    throw new ArgumentException("Can not find 'CompareTo()'" + differs[i], differs[i]); 
            }
            return ConstructDelegate(t, data);
        }
        private static Delegate ConstructDelegate(Type t, ComparisonData[] data)
        {
            DynamicMethod comp1 = ConstructMethod(nextComparisonMethodName, data, t);
            
            Type ComparisonOf = typeof(Comparison<>);
            Type ComparisonOfType = ComparisonOf.MakeGenericType(t);

            return comp1.CreateDelegate(ComparisonOfType);
        }
        private static DynamicMethod ConstructMethod(string methodName, ComparisonData[] data, Type t)
        {
            DynamicMethod comp = new DynamicMethod(methodName, typeof(int), new Type[] { t, t }, typeof(ILRSort));
            ILGenerator il = comp.GetILGenerator();
            LocalBuilder result = il.DeclareLocal(typeof(int));

            for (int i = 0; i < data.Length; i++)
            {
                il.DeclareLocal(data[i].EndCallType);
                il.DeclareLocal(data[i].EndCallType);
            }
            short locIndex = 1;
            LocalBuilder xIsNull = il.DeclareLocal(typeof(int));
            LocalBuilder yIsNull = il.DeclareLocal(typeof(int));

            Label end = il.DefineLabel();
            Label equal = il.DefineLabel();
            Label yGreater = il.DefineLabel();
            Label xGreater = il.DefineLabel();

            for (int i = 0; i < data.Length; i++)
            {
                Label xUnexpectedNull = il.DefineLabel();
                Label yUnexpectedNull = il.DefineLabel();

                Label yLoad = il.DefineLabel();
                Label nullsCheck = il.DefineLabel();
                Label xIsNOTZero = il.DefineLabel();


                il.Emit(OpCodes.Ldarg_0); // first object 
                data[i].SaveToLocal(locIndex, il, xUnexpectedNull);
                il.Emit(OpCodes.Br, yLoad); // if we are here - x not null


                il.MarkLabel(xUnexpectedNull);
                il.Emit(OpCodes.Pop); // first of all...
                il.Emit(OpCodes.Ldc_I4_1);
                il.Emit(OpCodes.Stloc, xIsNull);


                il.MarkLabel(yLoad);
                il.Emit(OpCodes.Ldarg_1);
                data[i].SaveToLocal(locIndex + 1, il, yUnexpectedNull);
                il.Emit(OpCodes.Br, nullsCheck); // if we are here - y not null


                il.MarkLabel(yUnexpectedNull);
                il.Emit(OpCodes.Pop); 
                il.Emit(OpCodes.Ldc_I4_1);
                il.Emit(OpCodes.Stloc, yIsNull);

                il.MarkLabel(nullsCheck);
                {
                    
                    // nulls check...
                    il.Emit(OpCodes.Ldloc, xIsNull);
                    il.Emit(OpCodes.Ldc_I4_1); 
                    il.Emit(OpCodes.Ceq);
                    // if we have 1 on stack - the x is null
                    il.Emit(OpCodes.Brfalse, xIsNOTZero);

                    il.Emit(OpCodes.Ldloc, yIsNull); 
                    il.Emit(OpCodes.Ldc_I4_1);
                    il.Emit(OpCodes.Ceq);
                    // y is null TOO. equal
                    il.Emit(OpCodes.Brtrue, equal);
                    // oh no....  y is not null, y is greater
                    il.Emit(OpCodes.Br, yGreater);


                    il.MarkLabel(xIsNOTZero); 
                    // ok then.... may be y is null?
                    il.Emit(OpCodes.Ldloc, yIsNull); 
                    il.Emit(OpCodes.Ldc_I4_1);
                    il.Emit(OpCodes.Ceq);
                    //yeap... null hehehe. x is greater
                    il.Emit(OpCodes.Brtrue, xGreater);
                    // no... both of them not nulls;
                }

                if (data[i].EndCallType.IsValueType)
                    il.Emit(OpCodes.Ldloca, locIndex);
                else
                    il.Emit(OpCodes.Ldloc, locIndex);

                il.Emit(OpCodes.Ldloc, locIndex + 1);
                MethodInfo endCompareTo = data[i].GetEndCompareTo();
                il.EmitCall(OpCodes.Callvirt, endCompareTo , null);

                locIndex += 2;

                if (!data[i].asc) il.Emit(OpCodes.Neg); //descending sort
                il.Emit(OpCodes.Dup);
                il.Emit(OpCodes.Stloc_0); // save rezult
                il.Emit(OpCodes.Brtrue, end);  // if non-zero - goto end. else - compare next
                if (i == data.Length - 1) // Если дальше ниче нет
                    il.Emit(OpCodes.Br, equal);

            }

            il.MarkLabel(xGreater); 
            il.Emit(OpCodes.Ldc_I4_1); // return 1;
            il.Emit(OpCodes.Ret);

            il.MarkLabel(yGreater); 
            il.Emit(OpCodes.Ldc_I4_M1); // return -1;
            il.Emit(OpCodes.Ret);

            il.MarkLabel(equal); 
            il.Emit(OpCodes.Ldc_I4_0); //return 0;
            il.Emit(OpCodes.Ret);

            il.MarkLabel(end); 
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ret);

            return comp;
        }
        private class  ComparisonData
        {
            /// <summary>
            /// Устанавливается одно из 3х. FieldInfo, PropertyInfo, callMethod
            /// </summary>
            public FieldInfo    fi = null;
            public PropertyInfo pi = null;
            public MethodInfo   callMethod = null;
            
            public bool asc = true;
            public ComparisonData subField = null;
            private Type parentType = null;

            public  MethodInfo  AccessMethod
            {
                get
                {
                    if (callMethod != null) return callMethod;
                    if (pi != null)
                    {
                        MethodInfo[] ars = pi.GetAccessors();
                        for (int i = 0; i < ars.Length; i++)
                        {
                            if (ars[i].Name.Contains("get_"))
                                return ars[i];
                        }
                    }
                    return null;                    
                }
            }
            private Type        callType
            {
                get
                {
                    if (fi != null) return fi.FieldType;
                    if (pi != null) return pi.PropertyType;
                    if (callMethod != null) return callMethod.ReturnType;
                    return null;
                }
            }
            public  Type        EndCallType
            {
                get
                {
                    if (subField == null) return callType;
                    ComparisonData ccd = subField;
                    while (ccd.subField != null)
                    {
                        ccd = ccd.subField;
                    }
                    return ccd.callType;                    
                }                
            }
            public  MethodInfo  GetEndCompareTo()
            {
                Type t = EndCallType;

                MethodInfo ct = t.GetMethod("CompareTo", new Type[] { t });
                if (ct == null) ct = t.GetMethod("CompareTo", new Type[] { typeof(object) });
                if (ct == null) return null;
                return ct;
            } 
            public         void SaveToLocal(int index, ILGenerator il, Label unexpectedNull)
            {
                SaveToLocal(this, index, il, unexpectedNull);
            }
            public static  void SaveToLocal(ComparisonData cd, int index, ILGenerator il, Label unexpectedNull)
            {
                if (!cd.callType.IsValueType) // Если на данном шаге мы получаем референс 
                {
                    // тогда все грузится как есть. Без адресов
                    if (cd.fi != null)
                        il.Emit(OpCodes.Ldfld, cd.fi);
                    else
                        il.EmitCall(OpCodes.Callvirt, cd.AccessMethod, null); 
                }
                else // Если валюэ тип
                    if (cd.subField == null) // если дальше ничего нет, то просто грузим значение
                    {
                        if (cd.fi != null)
                            il.Emit(OpCodes.Ldfld, cd.fi);
                        else
                            il.EmitCall(OpCodes.Call, cd.AccessMethod, null); 
                        // В любом случае на стэке ща лежит 1н валюэ тип
                    }
                    else // Если дальше есть еще методы или поля
                    {
                        // То вне зависимости от того что дальше нужно грузить адрес

                        if (cd.fi != null)
                            il.Emit(OpCodes.Ldflda, cd.fi);
                        else
                        {
                            //Зовем метод. результат - в локальную переменную. на стэк - ее адрес
                            il.EmitCall(OpCodes.Call, cd.AccessMethod, null);
                            LocalBuilder lb = il.DeclareLocal(cd.AccessMethod.ReturnType);
                            il.Emit(OpCodes.Stloc, lb);
                            il.Emit(OpCodes.Ldloca, lb.LocalIndex);
                        }
                    }

                ///Проверка на null
                if (!cd.callType.IsValueType) 
                {
                    il.Emit(OpCodes.Dup);
                    il.Emit(OpCodes.Ldnull);
                    il.Emit(OpCodes.Ceq);
                    il.Emit(OpCodes.Brtrue, unexpectedNull); // на стэке фигня осталась
                }
                else
                {
                    ///А вот тут нуллэйбл. Пошел он в жопу.
                }
                if (cd.subField != null)
                    SaveToLocal(cd.subField, index, il, unexpectedNull);
                else
                    il.Emit(OpCodes.Stloc, index);
            }
            /// <summary>
            /// Строит деревце вызовов, которыми можно добраться до значения
            /// </summary>
            /// <param name="section"></param>
            /// <returns></returns>
            public static ComparisonData ConstructSection(string section, Type t)
            {
                ComparisonData bcd = new ComparisonData();
                bcd.parentType = t;
                if (section.Contains("[desc]")) 
                    bcd.asc = false;
                section = section.Replace("[asc]", "").Replace("[desc]", "");

                string[] calls = section.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
                ComparisonData ccd = bcd;
                Type ct = t;
                for (int i = 0; i < calls.Length; i++)
                {
                    if (calls[i].Contains("()")) // method
                    {
                        calls[i] = calls[i].Replace("()", "");
                        try
                        {
                            MethodInfo mi = ct.GetMethod(calls[i], new Type[0]);
                            if (mi == null) throw new ArgumentException("Cant find " + calls[i], calls[i]);
                            ccd.callMethod = mi;
                        }
                        catch (AmbiguousMatchException ex)
                        {
                            throw new ArgumentException("AmbiguousMatch: " + calls[i], calls[i], ex);
                        }
                    }
                    else // field
                    {
                        FieldInfo fi = ct.GetField(calls[i]);
                        if (fi != null)
                        {
                            ccd.fi = fi;
                        }
                        else // property
                        {
                            try
                            {
                                PropertyInfo pi = ct.GetProperty(calls[i], new Type[0]);
                                if (pi != null)
                                {
                                    ccd.pi = pi;
                                }
                            }
                            catch (AmbiguousMatchException ex)
                            {
                                throw new ArgumentException("AmbiguousMatch: " + calls[i], calls[i], ex);
                            }
                        }
                    }
                    if (ccd.callType == null) throw new ArgumentException("Cant find " + calls[i], calls[i]);
                    ct = ccd.callType;

                    ccd.subField = new ComparisonData();
                    ccd.subField.parentType = ccd.callType;
                    ccd = ccd.subField;

                }
                ccd = bcd;
                // Уберемка лишнюю ссылу
                while (true)
                {
                    if (ccd.subField.subField == null)
                    {
                        ccd.subField = null;
                        break;
                    }
                    ccd = ccd.subField;
                }

                return bcd;
            }
            private ComparisonData() { }
        }
    }


    public class Tst
    {
        public int    field1 = 1;
        public string field2 = "field2";
        public double prop1
        {
            get { return (double)field1; }
        }
        public DateTime method1()
        {
            return DateTime.Now;
        }

        public static void Sort()
        {
            Tst[] tst; //List<Tst> tst;

            ILRSort.Sort(tst, "field1");
            ILRSort.Sort(tst, "field2[desc]"); //descending
            ILRSort.Sort(tst, "prop1, method1()[desc], field2"); 
            
            ILRSort.Sort(tst, "prop1, method1().Ticks.ToString()[desc]");
            ILRSort.Sort(tst, "prop1, prop1,prop1,prop1,prop1 "); // и т.д.
            ILRSort.Sort(tst, "method1().ToShortDateString().GetHashCode().ToString().Length.ToString()");//и т.п 
        }
    }
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.