2012-03-12 65 views
4

我在寫一個列表排序擴展方法。 我的輸入是列表和一個帶有屬性名稱和排序方向的字符串。 該字符串可以有多個屬性,像這樣: 「名稱ASC,日期降序」等列表動態鏈接列表<T> orderby

我已經實施的字符串解析和使用反射來從字符串獲取該屬性本身,而是什麼,我停留在現在我如何動態鏈接順序的方法。

類似: _list.orderBy(x=>x.prop1).thenBy(x=>x.prop2)

有什麼辦法來動態地建立這個?

+2

請參閱:http://stackoverflow.com/a/233505/861716 – 2012-03-12 09:54:36

回答

6

使用反射從字符串屬性名稱一個PropertyInfo的。然後,您可以使用PropertyInfo構建表達式樹來動態構造所有的orderbys。一旦你有了表達式樹,將它編譯成一個委託,(比如說Func,IEnumerable>)將_list參數傳遞給這個委託,並且它將給你有序結果作爲另一個枚舉。

,以便得到有關可枚舉泛型方法反映信息,看看答案對這個職位: Get a generic method without using GetMethods

public static class Helper 
{ 
    public static IEnumerable<T> BuildOrderBys<T>(
     this IEnumerable<T> source, 
     params SortDescription[] properties) 
    { 
     if (properties == null || properties.Length == 0) return source; 

     var typeOfT = typeof (T); 

     Type t = typeOfT; 

     IOrderedEnumerable<T> result = null; 
     var thenBy = false; 

     foreach (var item in properties 
      .Select(prop => new {PropertyInfo = t.GetProperty(prop.PropertyName), prop.Direction})) 
     { 
      var oExpr = Expression.Parameter(typeOfT, "o"); 
      var propertyInfo = item.PropertyInfo; 
      var propertyType = propertyInfo.PropertyType; 
      var isAscending = item.Direction == ListSortDirection.Ascending; 

      if (thenBy) 
      { 
       var prevExpr = Expression.Parameter(typeof (IOrderedEnumerable<T>), "prevExpr"); 
       var expr1 = Expression.Lambda<Func<IOrderedEnumerable<T>, IOrderedEnumerable<T>>>(
        Expression.Call(
         (isAscending ? thenByMethod : thenByDescendingMethod).MakeGenericMethod(typeOfT, propertyType), 
         prevExpr, 
         Expression.Lambda(
          typeof (Func<,>).MakeGenericType(typeOfT, propertyType), 
          Expression.MakeMemberAccess(oExpr, propertyInfo), 
          oExpr) 
         ), 
        prevExpr) 
        .Compile(); 

       result = expr1(result); 
      } 
      else 
      { 
       var prevExpr = Expression.Parameter(typeof (IEnumerable<T>), "prevExpr"); 
       var expr1 = Expression.Lambda<Func<IEnumerable<T>, IOrderedEnumerable<T>>>(
        Expression.Call(
         (isAscending ? orderByMethod : orderByDescendingMethod).MakeGenericMethod(typeOfT, propertyType), 
         prevExpr, 
         Expression.Lambda(
          typeof (Func<,>).MakeGenericType(typeOfT, propertyType), 
          Expression.MakeMemberAccess(oExpr, propertyInfo), 
          oExpr) 
         ), 
        prevExpr) 
        .Compile(); 

       result = expr1(source); 
       thenBy = true; 
      } 
     } 
     return result; 
    } 

    private static MethodInfo orderByMethod = 
     MethodOf(() => Enumerable.OrderBy(default(IEnumerable<object>), default(Func<object, object>))) 
      .GetGenericMethodDefinition(); 

    private static MethodInfo orderByDescendingMethod = 
     MethodOf(() => Enumerable.OrderByDescending(default(IEnumerable<object>), default(Func<object, object>))) 
      .GetGenericMethodDefinition(); 

    private static MethodInfo thenByMethod = 
     MethodOf(() => Enumerable.ThenBy(default(IOrderedEnumerable<object>), default(Func<object, object>))) 
      .GetGenericMethodDefinition(); 

    private static MethodInfo thenByDescendingMethod = 
     MethodOf(() => Enumerable.ThenByDescending(default(IOrderedEnumerable<object>), default(Func<object, object>))) 
      .GetGenericMethodDefinition(); 

    public static MethodInfo MethodOf<T>(Expression<Func<T>> method) 
    { 
     MethodCallExpression mce = (MethodCallExpression) method.Body; 
     MethodInfo mi = mce.Method; 
     return mi; 
    } 
} 

public static class Sample 
{ 
    private static void Main() 
    { 
     var data = new List<Customer> 
     { 
      new Customer {ID = 3, Name = "a"}, 
      new Customer {ID = 3, Name = "c"}, 
      new Customer {ID = 4}, 
      new Customer {ID = 3, Name = "b"}, 
      new Customer {ID = 2} 
     }; 

     var result = data.BuildOrderBys(
     new SortDescription("ID", ListSortDirection.Ascending), 
     new SortDescription("Name", ListSortDirection.Ascending) 
     ).Dump(); 
    } 
} 

public class Customer 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
} 

樣品的結果如圖LinqPad

enter image description here

+0

在我的示例中,我遞歸地構建表達式並編譯每個屬性,但是您可以使用Expression.Block構造整個表達式樹,然後只編譯一次。 – base2 2012-03-12 08:59:44

+1

如果您想使某些屬性降序排列,您可能還想擴展此示例以包含bool []遞增參數。 – base2 2012-03-12 10:14:52

+0

您的解決方案看起來非常好。謝謝。只有一件事,你有什麼想法集成對ASC和DESC排序的支持嗎?我又添加了兩個MethodInfo,但我無法找到如何從中繼續前進。 – 2012-03-12 10:17:58

0

您可以使用這樣的事情:

var query = _list.OrderBy(x=>x.prop1); 
if (shouldOrderByProp2Too) 
    query = query.ThenBy(x=>x.prop2); 
if (shouldOrderByProp3Too) 
    query = query.ThenBy(x=>x.prop3); 
// ... 
// then use query the way you had your code 

的評論: 在這種情況下,我會去工具在你的列表中的對象IComparable,然後就來排序/或使用Compareres動態檢查爲此 - 我能想到的唯一的其他事情就是用反射和動態調用來做到這一點...不是你想做的事情,如果不是真的需要的話)

+1

感謝您的回答,但我更希望有一種方法可用於任何N個屬性,如果可能的話。 – 2012-03-12 07:26:13

1

我不知道如何通過反射來添加訂單(太懶了),但這裏是僞代碼的基本概念:

var query = list.OrderBy(properties.First()); 
bool first = true; 
foreach(var property in properties.Skip(1)) 
{ 
    query = query.ThenBy(property); 
}