正在寻找一种更好的方法来对List 进行排序

我正在审查我不久前写的一段代码,我只是讨厌处理排序的方式 – 我想知道是否有人能够向我展示更好的方法。

我有一个class, Holding ,其中包含一些信息。 我有另一个类, HoldingsList ,它包含一个List成员。 我还有一个枚举, PortfolioSheetMapping ,它有大约40个元素。

它看起来像这样:

 public class Holding { public ProductInfo Product {get;set;} // ... various properties & methods ... } public class ProductInfo { // .. various properties, methods... } public class HoldingsList { public List Holdings {get;set;} // ... more code ... } public enum PortfolioSheetMapping { Unmapped = 0, Symbol, Quantitiy, Price, // ... more elements ... } 

我有一个方法可以调用List进行排序,具体取决于用户选择的枚举。 该方法使用mondo switch语句,有超过40个案例(呃!)。

下面的简短片段说明了代码:

 if (frm.SelectedSortColumn.IsBaseColumn) { switch (frm.SelectedSortColumn.BaseColumn) { case PortfolioSheetMapping.IssueId: if (frm.SortAscending) { // here I'm sorting the Holding instance's // Product.IssueId property values... // this is the pattern I'm using in the switch... pf.Holdings = pf.Holdings.OrderBy (c => c.Product.IssueId).ToList(); } else { pf.Holdings = pf.Holdings.OrderByDescending (c => c.Product.IssueId).ToList(); } break; case PortfolioSheetMapping.MarketId: if (frm.SortAscending) { pf.Holdings = pf.Holdings.OrderBy (c => c.Product.MarketId).ToList(); } else { pf.Holdings = pf.Holdings.OrderByDescending (c => c.Product.MarketId).ToList(); } break; case PortfolioSheetMapping.Symbol: if (frm.SortAscending) { pf.Holdings = pf.Holdings.OrderBy (c => c.Symbol).ToList(); } else { pf.Holdings = pf.Holdings.OrderByDescending (c => c.Symbol).ToList(); } break; // ... more code .... 

我的问题在于switch语句。 该switchPortfolioSheetMapping枚举紧密绑定,可以在明天或第二天更改。 每次更改时,我都必须重新访问此switch语句,并为其添加另一个case块。 我只是害怕最终这个转换声明会变得如此之大,以至于完全无法管理。

有人能告诉我是否有更好的方法来排序我的清单?

您正在将已排序的数据直接重新分配给您的pf.Holdings属性,那么为什么不绕过OrderByToList的开销而只是直接使用列表的Sort方法呢?

您可以使用映射来保存所有支持的排序的Comparison委托,然后使用适当的委托调用Sort(Comparison)

 if (frm.SelectedSortColumn.IsBaseColumn) { Comparison comparison; if (!_map.TryGetValue(frm.SelectedSortColumn.BaseColumn, out comparison)) throw new InvalidOperationException("Can't sort on BaseColumn"); if (frm.SortAscending) pf.Holdings.Sort(comparison); else pf.Holdings.Sort((x, y) => comparison(y, x)); } // ... private static readonly Dictionary> _map = new Dictionary> { { PortfolioSheetMapping.IssueId, GetComp(x => x.Product.IssueId) }, { PortfolioSheetMapping.MarketId, GetComp(x => x.Product.MarketId) }, { PortfolioSheetMapping.Symbol, GetComp(x => x.Symbol) }, // ... }; private static Comparison GetComp(Func selector) { return (x, y) => Comparer.Default.Compare(selector(x), selector(y)); } 

你可以尝试将开关减少到这样的:

  private static readonly Dictionary> sortingOperations = new Dictionary> { {PortfolioSheetMapping.Symbol, h => h.Symbol}, {PortfolioSheetMapping.Quantitiy, h => h.Quantitiy}, // more.... }; public static List SortHoldings(this List holdings, SortOrder sortOrder, PortfolioSheetMapping sortField) { if (sortOrder == SortOrder.Decreasing) { return holdings.OrderByDescending(sortingOperations[sortField]).ToList(); } else { return holdings.OrderBy(sortingOperations[sortField]).ToList(); } } 

您可以使用reflection填充sortingOperations,也可以手动维护它。 如果你不介意稍后在调用者中调用ToList,你也可以使SortHoldings接受并返回一个IEnumerable并删除ToList调用。 我不是100%确定OrderBy很高兴收到一个对象,但值得一试。

编辑:请参阅LukeH的解决方案以保持强类型。

你有没有看过Dynamic LINQ

具体来说,您可以简单地执行以下操作:

 var column = PortFolioSheetMapping.MarketId.ToString(); if (frm.SelectedSortColumn.IsBaseColumn) { if (frm.SortAscending) pf.Holdings = pf.Holdings.OrderBy(column).ToList(); else pf.Holdings = pf.Holdings.OrderByDescending(column).ToList(); } 

注意 :这确实存在枚举与您的列名匹配的约束(如果适合您)。

编辑

第一次错过了Product属性。 在这些情况下,DynamicLINQ需要查看例如"Product.ProductId" 。 您可以反映属性名称或仅使用“众所周知的”值并使用枚举.ToString() 。 在这一点上,我只是强迫我回答你的问题,这至少是一个有效的解决方案。

怎么样:

 Func sortBy; switch (frm.SelectedSortColumn.BaseColumn) { case PortfolioSheetMapping.IssueId: sortBy = c => c.Product.IssueId; break; case PortfolioSheetMapping.MarketId: sortBy = c => c.Product.MarketId; break; /// etc. } /// EDIT: can't use var here or it'll try to use IQueryable<> which doesn't Reverse() properly IEnumerable sorted = pf.Holdings.OrderBy(sortBy); if (!frm.SortAscending) { sorted = sorted.Reverse(); } 

这不是最快的解决方案,但它相当优雅,这就是你要求的!

编辑:哦,并且在case语句中,它可能需要重构一个单独的函数来返回一个Func,而不是一个完全摆脱它的好方法,但你至少可以将它隐藏在程序的中间!

在我看来,我们可以立即做出两项改进:

  • 使用frm.SortAscendingOrderByOrderByDesccending之间做出决定的逻辑在每种case都是重复的,并且可以在switch后拉出来,如果case被更改为除了建立排序键并将其置于Func之外什么也不做

  • 当然,它仍然会离开switch本身 – 这可以用一个静态地图(在Dictionary ,比如说)从PortfolioSheetMapping替换为一个Func取一个Holding并返回一个排序键。 例如

您可以实现使用reflection的自定义IComparer类。 然而,这会更慢。

这是我曾经使用的一个类:

 class ListComparer : IComparer { private ComparerState State = ComparerState.Init; public string Field {get;set;} public int Compare(object x, object y) { object cx; object cy; if (State == ComparerState.Init) { if (x.GetType().GetProperty(pField) == null) State = ComparerState.Field; else State = ComparerState.Property; } if (State == ComparerState.Property) { cx = x.GetType().GetProperty(Field).GetValue(x,null); cy = y.GetType().GetProperty(Field).GetValue(y,null); } else { cx = x.GetType().GetField(Field).GetValue(x); cy = y.GetType().GetField(Field).GetValue(y); } if (cx == null) if (cy == null) return 0; else return -1; else if (cy == null) return 1; return ((IComparable) cx).CompareTo((IComparable) cy); } private enum ComparerState { Init, Field, Property } } 

然后像这样使用它:

 var comparer = new ListComparer() { Field= frm.SelectedSortColumn.BaseColumn.ToString() }; if (frm.SortAscending) pf.Holding = pf.Holding.OrderBy(h=>h.Product, comparer).ToList(); else pf.Holding = pf.Holding.OrderByDescending(h=>h.Product, comparer).ToList(); 

如果Holding类(符号,价格等)中的属性是相同类型,则可以执行以下操作:

 var holdingList = new List() { new Holding() { Quantity = 2, Price = 5 }, new Holding() { Quantity = 7, Price = 2 }, new Holding() { Quantity = 1, Price = 3 } }; var lookup = new Dictionary>() { { PortfolioSheetMapping.Price, new Func(x => x.Price) }, { PortfolioSheetMapping.Symbol, new Func(x => x.Symbol) }, { PortfolioSheetMapping.Quantitiy, new Func(x => x.Quantity) } }; Console.WriteLine("Original values:"); foreach (var sortedItem in holdingList) { Console.WriteLine("Quantity = {0}, price = {1}", sortedItem.Quantity, sortedItem.Price); } var item = PortfolioSheetMapping.Price; Func action; if (lookup.TryGetValue(item, out action)) { Console.WriteLine("Values sorted by {0}:", item); foreach (var sortedItem in holdingList.OrderBy(action)) { Console.WriteLine("Quantity = {0}, price = {1}", sortedItem.Quantity, sortedItem.Price); } } 

然后显示:

Original values:
Quantity = 2, price = 5
Quantity = 7, price = 2
Quantity = 1, price = 3

Values sorted by Price:
Quantity = 7, price = 2
Quantity = 1, price = 3
Quantity = 2, price = 5