Looking for a better way to sort my List

后端 未结 7 1323
醉梦人生
醉梦人生 2021-01-02 18:37

I\'m reviewing a piece of code I wrote not too long ago, and I just hate the way I handled the sorting - I\'m wondering if anyone might be able to show me a better way.

相关标签:
7条回答
  • 2021-01-02 18:56

    Have you looked into Dynamic LINQ

    Specifically, you could simply do something like:

    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();
    }
    

    Note: This does have the constraint that your enum match your column names, if that suits you.

    EDIT

    Missed the Product property the first time. In these cases, DynamicLINQ is going to need to see, for example, "Product.ProductId". You could reflect the property name or simply use a 'well-known' value and concat with the enum .ToString(). At this point, I'm just really forcing my answer to your question so that it at least is a working solution.

    0 讨论(0)
  • 2021-01-02 18:56

    You could implement a custom IComparer class which uses reflection. However this would be slower.

    Here's a class, which I once used:

    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
        }
    }
    

    Then use it like this:

    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();
    
    0 讨论(0)
  • 2021-01-02 19:05

    how about:

    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();
    }
    

    ?

    Not exactly the fastest solution, but it's reasonably elegant, which is what you were asking for!

    EDIT: Oh, and with the case statement, it probably needs refactoring to a seperate function that returns a Func, not really a nice way to get rid of it entirely, but you can at least hide it away from the middle of your procedure !

    0 讨论(0)
  • 2021-01-02 19:08

    You could try reducing the switch to something like this:

        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();                
            }
        }
    

    You could populate sortingOperations with reflection, or maintain it by hand. You could also make SortHoldings accept and return an IEnumerable and remove the ToList calls if you don't mind calling ToList in the caller later. I'm not 100% sure that OrderBy is happy receiving an object, but worth a shot.

    Edit: See LukeH's solution to keep things strongly typed.

    0 讨论(0)
  • 2021-01-02 19:08

    It seems to me that there are two immediate improvements we can make:

    • the logic that uses frm.SortAscending to decide between OrderBy and OrderByDesccending is duplicated in every case, and can be pulled out to after the switch if the cases are changed to do nothing more than establishing the sort key and putting it in a Func

    • that still leaves the switch itself of course - and this can be replaced by a static map (in a Dictionary, say) from PortfolioSheetMapping to a Func taking a Holding and returning the sort key. eg

    0 讨论(0)
  • 2021-01-02 19:13

    If the properties in the Holding class (symbol, price etc) are the same type you can do the following:

    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);
        }
    }
    

    which then displays:

    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

    0 讨论(0)
提交回复
热议问题