我正在审查我不久前写的一段代码,我只是讨厌处理排序的方式 - 我想知道是否有人能够向我展示更好的方法。
我有一个类Holding
,其中包含一些信息。我有另一个类HoldingsList
,其中包含List<Holding>
成员。我还有一个枚举PortfolioSheetMapping
,它有大约40个元素。
看起来像这样:
public class Holding
{
public ProductInfo Product {get;set;}
// ... various properties & methods ...
}
public class ProductInfo
{
// .. various properties, methods...
}
public class HoldingsList
{
public List<Holding> 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语句。 switch
与PortfolioSheetMapping
enum紧密绑定,可以在明天或第二天更改。每次更改时,我都必须重新访问此switch语句,并向其添加另一个case
块。我只是害怕最终这个转换声明会变得如此之大,以至于完全无法管理。
有人可以告诉我是否有更好的方法对我的清单进行排序?
答案 0 :(得分:5)
您正在将已排序的数据直接重新分配回pf.Holdings
属性,因此为什么不绕过OrderBy
和ToList
的开销,只需使用列表的Sort
直接代替方法?
您可以使用地图来保留所有受支持的排序的Comparison<T>
个委托,然后使用相应的委托来调用Sort(Comparison<T>)
:
if (frm.SelectedSortColumn.IsBaseColumn)
{
Comparison<Holding> 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<PortfolioSheetMapping, Comparison<Holding>>
_map = new Dictionary<PortfolioSheetMapping, Comparison<Holding>>
{
{ PortfolioSheetMapping.IssueId, GetComp(x => x.Product.IssueId) },
{ PortfolioSheetMapping.MarketId, GetComp(x => x.Product.MarketId) },
{ PortfolioSheetMapping.Symbol, GetComp(x => x.Symbol) },
// ...
};
private static Comparison<Holding> GetComp<T>(Func<Holding, T> selector)
{
return (x, y) => Comparer<T>.Default.Compare(selector(x), selector(y));
}
答案 1 :(得分:4)
你可以尝试将开关减少到这样:
private static readonly Dictionary<PortfolioSheetMapping, Func<Holding, object>> sortingOperations = new Dictionary<PortfolioSheetMapping, Func<Holding, object>>
{
{PortfolioSheetMapping.Symbol, h => h.Symbol},
{PortfolioSheetMapping.Quantitiy, h => h.Quantitiy},
// more....
};
public static List<Holding> SortHoldings(this List<Holding> holdings, SortOrder sortOrder, PortfolioSheetMapping sortField)
{
if (sortOrder == SortOrder.Decreasing)
{
return holdings.OrderByDescending(sortingOperations[sortField]).ToList();
}
else
{
return holdings.OrderBy(sortingOperations[sortField]).ToList();
}
}
您可以使用反射填充sortingOperations,也可以手动维护它。如果你不介意稍后在调用者中调用ToList,你也可以让SortHoldings接受并返回一个IEnumerable并删除ToList调用。我不是100%确定OrderBy很乐意接收一个物体,但值得一试。
编辑:请参阅LukeH的解决方案以保持强类型。
答案 2 :(得分:3)
您是否查看了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()
连接。在这一点上,我只是强迫我回答你的问题,这至少是一个有效的解决方案。
答案 3 :(得分:1)
您可以实现使用反射的自定义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();
答案 4 :(得分:1)
怎么样:
Func<Holding, object> 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<Holding> sorted = pf.Holdings.OrderBy(sortBy);
if (!frm.SortAscending)
{
sorted = sorted.Reverse();
}
不是最快的解决方案,但它相当优雅,这就是你所要求的!
编辑: 哦,并且在case语句中,它可能需要重构一个单独的函数来返回一个Func,而不是一个完全摆脱它的好方法,但你至少可以将它隐藏在程序的中间!
答案 5 :(得分:1)
在我看来,我们可以立即做出两项改进:
使用frm.SortAscending
来决定OrderBy
和OrderByDesccending
之间的逻辑在每个case
中都是重复的,并且可以在{{1}之后被拉出来如果switch
被更改为除了建立排序键并将其放入case
当然仍然留下Func
本身 - 这可以替换为从switch
到{{1}的静态地图(在Dictionary
中)获取PortfolioSheetMapping
并返回排序键。例如
答案 6 :(得分:1)
如果Hold类中的属性(符号,价格等)属于同一类型,则可以执行以下操作:
var holdingList = new List<Holding>()
{
new Holding() { Quantity = 2, Price = 5 },
new Holding() { Quantity = 7, Price = 2 },
new Holding() { Quantity = 1, Price = 3 }
};
var lookup = new Dictionary<PortfolioSheetMapping, Func<Holding, int>>()
{
{ PortfolioSheetMapping.Price, new Func<Holding, int>(x => x.Price) },
{ PortfolioSheetMapping.Symbol, new Func<Holding, int>(x => x.Symbol) },
{ PortfolioSheetMapping.Quantitiy, new Func<Holding, int>(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<Holding, int> 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