简体   繁体   中英

C# how to sort a list without implementing IComparable manually?

I have a fairly complex scenario and I need to ensure items I have in a list are sorted.

Firstly the items in the list are based on a struct that contains a sub struct.

For example:

public struct topLevelItem
{
 public custStruct subLevelItem;
}

public struct custStruct
{
  public string DeliveryTime;
}

Now I have a list comprised of topLevelItems for example:

var items = new List<topLevelItem>();

I need a way to sort on the DeliveryTime ASC. What also adds to the complexity is that the DeliveryTime field is a string. Since these structs are part of a reusable API, I can't modify that field to a DateTime, neither can I implement IComparable in the topLevelItem class.

Any ideas how this can be done?

Thank you

Create a new type that implements IComparer and use an instance of it to compare the objects.

public class topLevelItemComparer : IComparer<topLevelItem>
{
    public int Compare(topLevelItem a, topLevelItem b)
    {
        // Compare and return here.
    }
}

You can then call Sort() like this:

var items = new List<topLevelItem>();
// Fill the List
items.Sort(new topLevelItemComparer());

It sounds like you need to get canonicalized date sorting even though your date is represented as a string, yes? Well, you can use LINQ's OrderBy operator, but you will have to parse the string into a date to achieve correct results:

items = items.OrderBy(item => DateTime.Parse(item.subLevelItem.DeliveryTime))
             .ToList(); 

Update:

I've added this in for completeness - a real example of how I use ParseExact with Invariant culture:

var returnMessagesSorted = returnMessages.OrderBy((item => DateTime.ParseExact(item.EnvelopeInfo.DeliveryTime, ISDSFunctions.GetSolutionDateTimeFormat(), CultureInfo.InvariantCulture)));
return returnMessagesSorted.ToList();

You can always implement a separate IComparer class, it's not fun, but it works well:

public class TopLevelItemComparer : IComparer<topLevelItem>
{
  public int Compare( topLevelItem x, topLevelItem y )
  {
      return DateTime.Parse(x.subLevelItem.DeliveryTime).CompareTo(
             DateTime.Parse(y.subLevelItem.DeliveryTime) );
  }
}

items.Sort( new TopLevelItemComparer() );

Be aware that most Sort() methods in the .NET framework accept an IComparer or IComparer<T> which allows you to redefine the comparison semantics for any type. Normally, you just use Comparer<T>.Default - or use an overload that essentially supplies this for you.

使用LINQ:

items = items.OrderBy(item => item.subLevelItem.DeliveryTime).ToList();

If you want to perform an in-place sort then you can use the Sort overload that takes a Comparison<T> argument and pass an anonymous function/lambda:

items.Sort((x, y) => DateTime.Parse(x.subLevelItem.DeliveryTime).CompareTo(
                     DateTime.Parse(y.subLevelItem.DeliveryTime)));

If you prefer to create a new sorted sequence rather than an in-place sort then LINQ's OrderBy is probably the way to go, as others have already mentioned.

Having had this problem before I once implemented a LambdaComparer that did the compare based on an arbitrary lambda expression. Not exact code but something along these lines:

public class LambdaComparer : IComparer<T>
{
    private Func<T,T,int> _func;

    public LambdaComparer(Func<T,T,int> function)
    {
        _func = function;
    }

    public int Compare(T x, T y)
    {
        return _func(x,y);
    }
}

Big advantage of this is you get a nice reusable chunk of code.

To sort the items list itself:

Comparison<topLevelItem> itemComparison = (x, y) => {
    DateTime dx;
    DateTime dy;

    bool xParsed = DateTime.TryParse(x.subLevelItem.DeliveryTime, out dx);
    bool yParsed = DateTime.TryParse(y.subLevelItem.DeliveryTime, out dy);

    if (xParsed && yParsed)
        return dx.CompareTo(dy);
    else if (xParsed)
        return -1; // or 1, if you want invalid strings to come first
    else if (yParsed)
        return 1; // or -1, if you want invalid strings to come first
    else
        // simple string comparison
        return x.subLevelItem.DeliveryTime.CompareTo(y.subLevelItem.DeliveryTime);
};

items.Sort(itemComparison);

This approach has the advantage of:

  1. Sorting the list in place (that is, if you actualy want the list sorted in-place)
  2. Sorting by actual DateTime values, rather than strings, BUT ...
  3. Not throwing an exception if a string does not represent a valid DateTime (basically, all the invalid strings will end up on one side of the list)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM