C#中的List.Sort:使用null对象调用comparer

时间:2009-06-22 08:18:26

标签: c# list sorting

使用内置C#List.Sort函数和自定义比较器,我会遇到奇怪的行为。

由于某种原因,它有时会将比较器类的Compare方法作为参数之一调用null对象。但是,如果我使用调试器检查列表,则集合中没有空对象。

我的比较器类看起来像这样:

public class DelegateToComparer<T> : IComparer<T>
{
    private readonly Func<T,T,int> _comparer;

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

    public DelegateToComparer(Func<T, T, int> comparer)
    {
        _comparer = comparer;
    }
}

这允许委托传递给List.Sort方法,如下所示:

mylist.Sort(new DelegateToComparer<MyClass>(
    (x, y) => { 
         return x.SomeProp.CompareTo(y.SomeProp); 
     });

因此,上面的委托将为 x 参数抛出一个空引用异常,即使 mylist 的元素都不为空。

更新:是的我绝对相信参数 x 会抛出空引用异常!

UPDATE:我没有使用框架的List.Sort方法,而是尝试了自定义排序方法(即 new BubbleSort()。Sort(mylist))和问题走了。我怀疑,List.Sort方法由于某种原因将null传递给比较器。

7 个答案:

答案 0 :(得分:22)

当比较函数不一致时会发生这个问题,例如x&lt; y并不总是暗示y&lt; X。在您的示例中,您应该检查如何比较SomeProp类型的两个实例。

这是一个重现问题的例子。这里,它是由病理比较功能“compareStrings”引起的。它取决于列表的初始状态:如果您将初始订单更改为“C”,“B”,“A”,则没有例外。

我不会在Sort函数中将其称为错误 - 这只是比较函数一致的要求。

using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var letters = new List<string>{"B","C","A"};

        letters.Sort(CompareStrings);
    }

    private static int CompareStrings(string l, string r)
    {
        if (l == "B")
            return -1;

        return l.CompareTo(r);
    }
}

答案 1 :(得分:3)

您确定问题不是SomePropnull吗?

特别是使用字符串或Nullable<T>值。

使用字符串,最好使用:

list.Sort((x, y) => string.Compare(x.SomeProp, y.SomeProp));

(编辑)

对于null-safe包装器,您可以使用Comparer<T>.Default - 例如,按属性对列表进行排序:

using System;
using System.Collections.Generic;
public static class ListExt {
    public static void Sort<TSource, TValue>(
            this List<TSource> list,
            Func<TSource, TValue> selector) {
        if (list == null) throw new ArgumentNullException("list");
        if (selector == null) throw new ArgumentNullException("selector");
        var comparer = Comparer<TValue>.Default;
        list.Sort((x,y) => comparer.Compare(selector(x), selector(y)));
    }
}
class SomeType {
    public override string ToString() { return SomeProp; }
    public string SomeProp { get; set; }
    static void Main() {
        var list = new List<SomeType> {
            new SomeType { SomeProp = "def"},
            new SomeType { SomeProp = null},
            new SomeType { SomeProp = "abc"},
            new SomeType { SomeProp = "ghi"},
        };
        list.Sort(x => x.SomeProp);
        list.ForEach(Console.WriteLine);
    }
}

答案 2 :(得分:3)

我也遇到过这个问题(将null引用传递给我的自定义IComparer实现),最后发现问题是由于使用了不一致的比较函数。

这是我最初的IComparer实现:

public class NumericStringComparer : IComparer<String>
{
    public int Compare(string x, string y)
    {
        float xNumber, yNumber;
        if (!float.TryParse(x, out xNumber))
        {
            return -1;
        }
        if (!float.TryParse(y, out yNumber))
        {
            return -1;
        }
        if (xNumber == yNumber)
        {
            return 0;
        }
        else
        {
            return (xNumber > yNumber) ? 1 : -1;
        }
    }
}

这段代码中的错误是,只要其中一个值无法正确解析,Compare就会返回-1(在我的情况下,这是由于数值错误格式化的字符串表示,因此TryParse总是失败)。

请注意,如果x和y的格式都不正确(因此TryParse都失败了),调用Compare(x,y)和Compare(y,x)会产生相同的结果:-1。我认为这是主要问题。在调试时,即使正在排序的集合没有包含空字符串,Compare()也会在某些时候将null字符串指针作为其参数之一传递。

一旦我修复了TryParse问题并确保了我的实现的一致性,问题便消失了,并且Compare不再被传递空指针。

答案 3 :(得分:0)

马克的回答很有用。我同意他的说法,NullReference是由于在null属性上调用CompareTo。无需扩展类,您可以执行以下操作:

mylist.Sort((x, y) => 
      (Comparer<SomePropType>.Default.Compare(x.SomeProp, y.SomeProp)));

其中SomePropType是SomeProp的类型

答案 4 :(得分:0)

出于调试目的,您希望方法为空安全。 (或者至少,捕获null-ref。异常,并以一些硬编码的方式处理它)。然后,使用调试器观察其他值的比较,按什么顺序,以及哪些调用成功或失败。

然后你会找到你的答案,然后你可以删除无效安全。

答案 5 :(得分:0)

你可以运行这段代码......

mylst.Sort((i, j) =>
              {
                  Debug.Assert(i.SomeProp != null && j.SomeProp != null);
                  return i.SomeProp.CompareTo(j.SomeProp);
              }
         );

答案 6 :(得分:0)

我自己偶然发现了这个问题,发现它与我输入的NaN属性有关。这是一个应该产生异常的最小测试用例:

public class C {

    double v;

    public static void Main() {
        var test =
            new List<C> { new C { v = 0d },
                          new C { v = Double.NaN },
                          new C { v = 1d } };
        test.Sort((d1, d2) => (int)(d1.v - d2.v));
    }

}