实现多个Linq OrderBy组合的IComparer

5
我的问题是我总是想按照特定的方式对一组对象进行排序。
例如:
class foo{
public string name {get;set;}
public DateTime date {get;set;}
public int counter {get;set;}
}

...

IEnumerable<foo> dosomething(foo[] bar){ 
return bar.OrderBy(a=>a.name).ThenBy(a=>a.date).ThenBy(a=>a.counter);
}

我遇到的问题是每次都需要冗长地添加排序顺序。一个简洁的解决方案似乎是创建一个实现 IComparer<foo> 接口的类,这意味着我可以这样做:
IEnumerable<foo> dosomething(foo[] bar){ 
return bar.OrderBy(a=>a, new fooIComparer())
}

问题在于,该实现的排序方法如下:

...

public int Compare(foo x, foo y){ }

意思是它进行了非常细致的比较。
当前的实现(可能会起作用,尽管我正在编写伪代码)。
public int Compare(foo x, foo y){
if (x==y)
  return 0;
var order = new []{x,y}.OrderBy(a=>a.name).ThenBy(a=>a.date).ThenBy(a=>a.counter);
  return (order[0] == x) ? -1 : -1;//if x is first in array it is less than y, else it is greater
}

这并不是很高效的方法,有没有其他更简洁的解决方案?最好不需要使用Compare(x,y)方法。


1
我不太明白这里的问题是什么。您是否有一个动态集合,应始终按某个特定顺序生成其内容?使用例如具有自定义比较器的 SortedSet,一切都会自动发生(注意:SortedSet 不允许重复项)。 - Jon
不要因为“dynamic”关键字而混淆问题,但是集合中的项目可能每次都是唯一/不同的。我现在正在查看“SortedSet”... - maxp
我所说的“动态”是指“有东西一直在进出集合中”。如果它是你建立一次然后不修改的东西,你可以在建立后简单地进行一次排序并完成它。 - Jon
4个回答

4

选项1 - 比较器

由于您需要按多个条件排序,因此您需要在每种情况下分别检查它们;例如,如果x.namey.name相等,则应检查x.datey.date,以此类推。

public class FooComparer : IComparer<Foo>
{
    public int Compare(Foo x, Foo y)
    {
       // nasty null checks!
        if (x == null || y == null)
        {
            return x == y ? 0
                : x == null ? -1
                : 1;
        }

        // if the names are different, compare by name
        if (!string.Equals(x.Name, y.Name))
        {
            return string.Compare(x.Name, y.Name);
        }

        // if the dates are different, compare by date
        if (!DateTime.Equals(x.Date, y.Date))
        {
            return DateTime.Compare(x.Date, y.Date);
        }

        // finally compare by the counter
        return x.Counter.CompareTo(y.Counter);
    }
}

选项2 - 扩展方法

另一种不那么吸引人的方法是使用扩展方法。遗憾的是,由于每个ThenByTKey可能不同,我们失去了泛型的优势,但在这种情况下可以安全地用object类型替换。

public static IOrderedEnumerable<T> OrderByThen<T>(this IEnumerable<T> source, Func<T, object> selector, params Func<T, object>[] thenBySelectors)
{
    IOrderedEnumerable<T> ordered = source.OrderBy(selector);
    foreach (Func<T, object> thenBy in thenBySelectors)
    {
        ordered = ordered.ThenBy(thenBy);
    }

    return ordered;
}

将在 x.name == null 抛出异常。 - Rafal
3
我可能错了,但是在你的例子中,当x为空时尝试查看x.name不会产生相同的结果吗? - maxp

2

您需要实现 IComparable<foo> 并比较所有属性:

class foo: IComparable<foo>, IComparer<foo>
{
    public string name { get; set; }
    public DateTime date { get; set; }
    public int counter { get; set; }

    public int Compare(foo x, foo y)
    {
        if (x == null || y == null) return int.MinValue;
        if (x.name != y.name)
            return StringComparer.CurrentCulture.Compare(x.name, y.name);
        else if (x.date != y.date)
            return x.date.CompareTo(y.date);
        else if (x.counter != y.counter)
            return x.counter.CompareTo(y.counter);
        else
            return 0;
    }

    public int CompareTo(foo other)
    {
        return Compare(this, other);
    }
}

然后你可以这样使用 OrderBy

var ordered = foos.OrderBy(f => f).ToList();

1

扩展方法有什么问题吗?


虽然我不是很喜欢扩展方法,但我完全没有想到可以使用其中之一,现在正在研究一下... - maxp

1
为什么不简单地比较你的值:

int Compare(foo x, foo y)
{

    if (x== null && y == null)
        return 0;
    else if (x == null)
        return -1;
    else if (y == null)
        return 1;

    var nameComparision = string.Compare(x.name,y.name);
    if (nameComparision != 0)
        return nameComparision;
    var dateComparision = x.date.CompareTo(y.date);
    if (dateComparision != 0)
        return dateComparision;
    var counterComparision  = x.counter.CompareTo(y.counter);
    return counterComparision;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接