由于整数溢出,OrderByDescending 操作不正确

OrderByDescending operates incorrectly due to integer overflow

在浏览 Enumerable class 的 .Net Framework 源代码时,我发现用于排序的内部 EnumerableSorter class CompareKeys method 有以下一行:

return descending ? -c : c;

其中 cIComparer.Compare Method (T, T) 方法调用的结果,实际上并不强制我们只使用 -1、1 或 0 来表示排序。

考虑到-Int32.MinValue == Int32.MinValue由于整数溢出,会导致排序不正确,可以通过以下代码片段证明:

public class Value : IComparable<Value>
{
    private readonly Int32 _value;
    public Value(Int32 value)
    {
        this._value = value;
    }

    public Int32 CompareTo(Value other)
    {
        if (other == null)
            throw new ArgumentNullException(nameof(other));
        var cmp = this._value.CompareTo(other._value);
        if (cmp > 0)
            return Int32.MaxValue;
        if (cmp < 0)
            return Int32.MinValue;
        return 0;
    }

    public override String ToString()
    {
        return this._value.ToString();
    }
}

private static void Print<T>(String header, IEnumerable<T> values)
{
    Console.WriteLine(header);
    foreach (var item in values)
    {
        Console.WriteLine(item);
    }
    Console.WriteLine();
}

public static void Main()
{
    try
    {
        var notSorted = new[] { 1, 3, 2 }
            .Select(i => 
                new Value(i))
            .ToArray();

        Print("Not sorted", notSorted);
        Print("Sorted by", notSorted.OrderBy(item => item));
        Print("Sorted by descending", notSorted.OrderByDescending(item => item));
    }
    catch (Exception exc)
    {
        Console.WriteLine(exc);
    }
    Console.WriteLine("Press any key...");
    Console.ReadKey(true);         
}

对于 OrderByDescending 它产生:

Sorted by descending
3
1
2

这是预料之中的,但也是一个明显不正确的结果。

所以它似乎是 .Net 中的一个缺陷,但如果 CompareTo 以合理的方式实现,则不太可能发生。我说得对吗?

更新:

正如SLaks the issue has been known for a long time but hasn't been fixed despite all the new releases - https://connect.microsoft.com/VisualStudio/feedback/details/634949/orderbydescending-fails-in-linq-to-objects-when-a-comparer-returns-int-minvalue

指出的那样

正如usr .Net Core has this issue fixed - https://github.com/dotnet/corefx/blob/35e03c78d89d02f2d3b4a1f8b277a35c88f45750/src/System.Linq/src/System/Linq/OrderedEnumerable.cs#L628

指出的那样

似乎没有太多的答案,所以:

正如 SLaks 指出的那样,这个问题早已为人所知,但 从未在 .NET Framework 中得到修复 尽管有所有新版本(.Net 4.6.1 作为现在)- https://connect.microsoft.com/VisualStudio/feedback/details/634949/orderbydescending-fails-in-linq-to-objects-when-a-comparer-returns-int-minvalue.

避免此问题的唯一方法是不 return 来自 CompareTo 实现的 Int32.MinValue


但正如 usr 指出的那样。Net Core 已修复此问题 - https://github.com/dotnet/corefx/blob/35e03c78d89d02f2d3b4a1f8b277a35c88f45750/src/System.Linq/src/System/Linq/OrderedEnumerable.cs#L628