По набору названий полей или методов класс генерит динамический 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()");//и т.п
Исходник
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()");//и т.п
}
}
}