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

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

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

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

public class DelegateToComparer : IComparer { private readonly Func _comparer; public int Compare(T x, T y) { return _comparer(x, y); } public DelegateToComparer(Func comparer) { _comparer = comparer; } } 

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

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

因此,即使没有mylist的元素为null,上面的委托也会为x参数抛出一个空引用exception。

更新:是的我绝对相信它是参数x抛出空引用exception!

更新:我没有使用框架的List.Sort方法,而是尝试了一种自定义排序方法(即新的BubbleSort()。排序(mylist) ),问题就消失了。 正如我所怀疑的,List.Sort方法由于某种原因将null传递给比较器。

当比较函数不一致时会发生此问题,因此x

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

我不会将此称为Sort函数中的错误 – 它只是比较函数一致的要求。

 using System.Collections.Generic; class Program { static void Main() { var letters = new List{"B","C","A"}; letters.Sort(CompareStrings); } private static int CompareStrings(string l, string r) { if (l == "B") return -1; return l.CompareTo(r); } } 

你确定问题不是SomePropnull吗?

特别是,使用字符串或Nullable值。

使用字符串,最好使用:

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

(编辑)

对于null安全包装器,您可以使用Comparer.Default – 例如,按属性对列表进行排序:

 using System; using System.Collections.Generic; public static class ListExt { public static void Sort( this List list, Func selector) { if (list == null) throw new ArgumentNullException("list"); if (selector == null) throw new ArgumentNullException("selector"); var comparer = Comparer.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 { 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); } } 

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

这是我最初的IComparer实现:

 public class NumericStringComparer : IComparer { 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不再被传递空指针。

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

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

其中SomePropType是SomeProp的类型

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

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

你能运行这段代码吗?

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

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

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