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
你确定问题不是SomeProp
是null
吗?
特别是,使用字符串或Nullable
值。
使用字符串,最好使用:
list.Sort((x, y) => string.Compare(x.SomeProp, y.SomeProp));
(编辑)
对于null安全包装器,您可以使用Comparer
– 例如,按属性对列表进行排序:
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)); } }