用 C # 对 IList 进行排序

我今天遇到了一个有趣的问题。我们有一个返回 ILlist 的 WCF Web 服务。在我想解决之前,这没什么大不了的。

原来 IList 接口没有内置排序方法。

我最终使用 ArrayList.Adapter(list).Sort(new MyComparer())方法来解决这个问题,但它对我来说似乎有点“贫民区”。

我曾尝试编写一个扩展方法,也曾尝试从 IList 继承并实现我自己的 Sort ()方法以及转换为 List,但这些似乎都不太优雅。

所以我的问题是,有没有人有一个优雅的解决方案来排序 IList

123015 次浏览

You're going to have to do something like that i think (convert it into a more concrete type).

Maybe take it into a List of T rather than ArrayList, so that you get type safety and more options for how you implement the comparer.

Convert your IList into List<T> or some other generic collection and then you can easily query/sort it using System.Linq namespace (it will supply bunch of extension methods)

How about using LINQ To Objects to sort for you?

Say you have a IList<Car>, and the car had an Engine property, I believe you could sort as follows:

from c in list
orderby c.Engine
select c;

Edit: You do need to be quick to get answers in here. As I presented a slightly different syntax to the other answers, I will leave my answer - however, the other answers presented are equally valid.

You can use LINQ:

using System.Linq;


IList<Foo> list = new List<Foo>();
IEnumerable<Foo> sortedEnum = list.OrderBy(f=>f.Bar);
IList<Foo> sortedList = sortedEnum.ToList();

Here's an example using the stronger typing. Not sure if it's necessarily the best way though.

static void Main(string[] args)
{
IList list = new List<int>() { 1, 3, 2, 5, 4, 6, 9, 8, 7 };
List<int> stronglyTypedList = new List<int>(Cast<int>(list));
stronglyTypedList.Sort();
}


private static IEnumerable<T> Cast<T>(IEnumerable list)
{
foreach (T item in list)
{
yield return item;
}
}

The Cast function is just a reimplementation of the extension method that comes with 3.5 written as a normal static method. It is quite ugly and verbose unfortunately.

In VS2008, when I click on the service reference and select "Configure Service Reference", there is an option to choose how the client de-serializes lists returned from the service.

Notably, I can choose between System.Array, System.Collections.ArrayList and System.Collections.Generic.List

Found a good post on this and thought I'd share. Check it out HERE

Basically.

You can create the following class and IComparer Classes

public class Widget {
public string Name = string.Empty;
public int Size = 0;


public Widget(string name, int size) {
this.Name = name;
this.Size = size;
}
}


public class WidgetNameSorter : IComparer<Widget> {
public int Compare(Widget x, Widget y) {
return x.Name.CompareTo(y.Name);
}
}


public class WidgetSizeSorter : IComparer<Widget> {
public int Compare(Widget x, Widget y) {
return x.Size.CompareTo(y.Size);
}
}

Then If you have an IList, you can sort it like this.

List<Widget> widgets = new List<Widget>();
widgets.Add(new Widget("Zeta", 6));
widgets.Add(new Widget("Beta", 3));
widgets.Add(new Widget("Alpha", 9));


widgets.Sort(new WidgetNameSorter());
widgets.Sort(new WidgetSizeSorter());

But Checkout this site for more information... Check it out HERE

using System.Linq;


var yourList = SomeDAO.GetRandomThings();
yourList.ToList().Sort( (thing, randomThing) => thing.CompareThisProperty.CompareTo( randomThing.CompareThisProperty ) );

That's pretty !ghetto.

Found this thread while I was looking for a solution to the exact problem described in the original post. None of the answers met my situation entirely, however. Brody's answer was pretty close. Here is my situation and solution I found to it.

I have two ILists of the same type returned by NHibernate and have emerged the two IList into one, hence the need for sorting.

Like Brody said I implemented an ICompare on the object (ReportFormat) which is the type of my IList:

 public class FormatCcdeSorter:IComparer<ReportFormat>
{
public int Compare(ReportFormat x, ReportFormat y)
{
return x.FormatCode.CompareTo(y.FormatCode);
}
}

I then convert the merged IList to an array of the same type:

ReportFormat[] myReports = new ReportFormat[reports.Count]; //reports is the merged IList

Then sort the array:

Array.Sort(myReports, new FormatCodeSorter());//sorting using custom comparer

Since one-dimensional array implements the interface System.Collections.Generic.IList<T>, the array can be used just like the original IList.

Is this a valid solution?

        IList<string> ilist = new List<string>();
ilist.Add("B");
ilist.Add("A");
ilist.Add("C");


Console.WriteLine("IList");
foreach (string val in ilist)
Console.WriteLine(val);
Console.WriteLine();


List<string> list = (List<string>)ilist;
list.Sort();
Console.WriteLine("List");
foreach (string val in list)
Console.WriteLine(val);
Console.WriteLine();


list = null;


Console.WriteLine("IList again");
foreach (string val in ilist)
Console.WriteLine(val);
Console.WriteLine();

The result was: IList B A C

List A B C

IList again A B C

Useful for grid sorting this method sorts list based on property names. As follow the example.

    List<MeuTeste> temp = new List<MeuTeste>();


temp.Add(new MeuTeste(2, "ramster", DateTime.Now));
temp.Add(new MeuTeste(1, "ball", DateTime.Now));
temp.Add(new MeuTeste(8, "gimm", DateTime.Now));
temp.Add(new MeuTeste(3, "dies", DateTime.Now));
temp.Add(new MeuTeste(9, "random", DateTime.Now));
temp.Add(new MeuTeste(5, "call", DateTime.Now));
temp.Add(new MeuTeste(6, "simple", DateTime.Now));
temp.Add(new MeuTeste(7, "silver", DateTime.Now));
temp.Add(new MeuTeste(4, "inn", DateTime.Now));


SortList(ref temp, SortDirection.Ascending, "MyProperty");


private void SortList<T>(
ref List<T> lista
, SortDirection sort
, string propertyToOrder)
{
if (!string.IsNullOrEmpty(propertyToOrder)
&& lista != null
&& lista.Count > 0)
{
Type t = lista[0].GetType();


if (sort == SortDirection.Ascending)
{
lista = lista.OrderBy(
a => t.InvokeMember(
propertyToOrder
, System.Reflection.BindingFlags.GetProperty
, null
, a
, null
)
).ToList();
}
else
{
lista = lista.OrderByDescending(
a => t.InvokeMember(
propertyToOrder
, System.Reflection.BindingFlags.GetProperty
, null
, a
, null
)
).ToList();
}
}
}

This question inspired me to write a blog post: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/

I think that, ideally, the .NET Framework would include a static sorting method that accepts an IList<T>, but the next best thing is to create your own extension method. It's not too hard to create a couple of methods that will allow you to sort an IList<T> as you would a List<T>. As a bonus you can overload the LINQ OrderBy extension method using the same technique, so that whether you're using List.Sort, IList.Sort, or IEnumerable.OrderBy, you can use the exact same syntax.

public static class SortExtensions
{
//  Sorts an IList<T> in place.
public static void Sort<T>(this IList<T> list, Comparison<T> comparison)
{
ArrayList.Adapter((IList)list).Sort(new ComparisonComparer<T>(comparison));
}


// Sorts in IList<T> in place, when T is IComparable<T>
public static void Sort<T>(this IList<T> list) where T: IComparable<T>
{
Comparison<T> comparison = (l, r) => l.CompareTo(r);
Sort(list, comparison);


}


// Convenience method on IEnumerable<T> to allow passing of a
// Comparison<T> delegate to the OrderBy method.
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> list, Comparison<T> comparison)
{
return list.OrderBy(t => t, new ComparisonComparer<T>(comparison));
}
}


// Wraps a generic Comparison<T> delegate in an IComparer to make it easy
// to use a lambda expression for methods that take an IComparer or IComparer<T>
public class ComparisonComparer<T> : IComparer<T>, IComparer
{
private readonly Comparison<T> _comparison;


public ComparisonComparer(Comparison<T> comparison)
{
_comparison = comparison;
}


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


public int Compare(object o1, object o2)
{
return _comparison((T)o1, (T)o2);
}
}

With these extensions, sort your IList just like you would a List:

IList<string> iList = new []
{
"Carlton", "Alison", "Bob", "Eric", "David"
};


// Use the custom extensions:


// Sort in-place, by string length
iList.Sort((s1, s2) => s1.Length.CompareTo(s2.Length));


// Or use OrderBy()
IEnumerable<string> ordered = iList.OrderBy((s1, s2) => s1.Length.CompareTo(s2.Length));

There's more info in the post: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/

try this  **USE ORDER BY** :


public class Employee
{
public string Id { get; set; }
public string Name { get; set; }
}


private static IList<Employee> GetItems()
{
List<Employee> lst = new List<Employee>();


lst.Add(new Employee { Id = "1", Name = "Emp1" });
lst.Add(new Employee { Id = "2", Name = "Emp2" });
lst.Add(new Employee { Id = "7", Name = "Emp7" });
lst.Add(new Employee { Id = "4", Name = "Emp4" });
lst.Add(new Employee { Id = "5", Name = "Emp5" });
lst.Add(new Employee { Id = "6", Name = "Emp6" });
lst.Add(new Employee { Id = "3", Name = "Emp3" });


return lst;
}


**var lst = GetItems().AsEnumerable();


var orderedLst = lst.OrderBy(t => t.Id).ToList();


orderedLst.ForEach(emp => Console.WriteLine("Id - {0} Name -{1}", emp.Id, emp.Name));**

The accepted answer by @DavidMills is quite good, but I think it can be improved upon. For one, there is no need to define the ComparisonComparer<T> class when the framework already includes a static method Comparer<T>.Create(Comparison<T>). This method can be used to create an IComparison on the fly.

Also, it casts IList<T> to IList which has the potential to be dangerous. In most cases that I have seen, List<T> which implements IList is used behind the scenes to implement IList<T>, but this is not guaranteed and can lead to brittle code.

Lastly, the overloaded List<T>.Sort() method has 4 signatures and only 2 of them are implemented.

  1. List<T>.Sort()
  2. List<T>.Sort(Comparison<T>)
  3. List<T>.Sort(IComparer<T>)
  4. List<T>.Sort(Int32, Int32, IComparer<T>)

The below class implements all 4 List<T>.Sort() signatures for the IList<T> interface:

using System;
using System.Collections.Generic;


public static class IListExtensions
{
public static void Sort<T>(this IList<T> list)
{
if (list is List<T> listImpl)
{
listImpl.Sort();
}
else
{
var copy = new List<T>(list);
copy.Sort();
Copy(copy, 0, list, 0, list.Count);
}
}


public static void Sort<T>(this IList<T> list, Comparison<T> comparison)
{
if (list is List<T> listImpl)
{
listImpl.Sort(comparison);
}
else
{
var copy = new List<T>(list);
copy.Sort(comparison);
Copy(copy, 0, list, 0, list.Count);
}
}


public static void Sort<T>(this IList<T> list, IComparer<T> comparer)
{
if (list is List<T> listImpl)
{
listImpl.Sort(comparer);
}
else
{
var copy = new List<T>(list);
copy.Sort(comparer);
Copy(copy, 0, list, 0, list.Count);
}
}


public static void Sort<T>(this IList<T> list, int index, int count,
IComparer<T> comparer)
{
if (list is List<T> listImpl)
{
listImpl.Sort(index, count, comparer);
}
else
{
var range = new List<T>(count);
for (int i = 0; i < count; i++)
{
range.Add(list[index + i]);
}
range.Sort(comparer);
Copy(range, 0, list, index, count);
}
}


private static void Copy<T>(IList<T> sourceList, int sourceIndex,
IList<T> destinationList, int destinationIndex, int count)
{
for (int i = 0; i < count; i++)
{
destinationList[destinationIndex + i] = sourceList[sourceIndex + i];
}
}
}

Usage:

class Foo
{
public int Bar;


public Foo(int bar) { this.Bar = bar; }
}


void TestSort()
{
IList<int> ints = new List<int>() { 1, 4, 5, 3, 2 };
IList<Foo> foos = new List<Foo>()
{
new Foo(1),
new Foo(4),
new Foo(5),
new Foo(3),
new Foo(2),
};


ints.Sort();
foos.Sort((x, y) => Comparer<int>.Default.Compare(x.Bar, y.Bar));
}

The idea here is to leverage the functionality of the underlying List<T> to handle sorting whenever possible. Again, most IList<T> implementations that I have seen use this. In the case when the underlying collection is a different type, fallback to creating a new instance of List<T> with elements from the input list, use it to do the sorting, then copy the results back to the input list. This will work even if the input list does not implement the IList interface.

This looks MUCH MORE SIMPLE if you ask me. This works PERFECTLY for me.

You could use Cast() to change it to IList then use OrderBy():

    var ordered = theIList.Cast<T>().OrderBy(e => e);

WHERE T is the type eg. Model.Employee or Plugin.ContactService.Shared.Contact

Then you can use a for loop and its DONE.

  ObservableCollection<Plugin.ContactService.Shared.Contact> ContactItems= new ObservableCollection<Contact>();


foreach (var item in ordered)
{
ContactItems.Add(item);
}