寻找一种更好的方法来排序我的List <t> </t>

时间:2010-06-29 13:47:50

标签: c# .net-3.5 c#-3.0

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

我有一个类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语句。 switchPortfolioSheetMapping enum紧密绑定,可以在明天或第二天更改。每次更改时,我都必须重新访问此switch语句,并向其添加另一个case块。我只是害怕最终这个转换声明会变得如此之大,以至于完全无法管理。

有人可以告诉我是否有更好的方法对我的清单进行排序?

7 个答案:

答案 0 :(得分:5)

您正在将已排序的数据直接重新分配回pf.Holdings属性,因此为什么不绕过OrderByToList的开销,只需使用列表的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来决定OrderByOrderByDesccending之间的逻辑在每个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