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

cbp*_*cbp 20 c# sorting list

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

出于某种原因,它有时会将comparer类的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

Lee*_*Lee 23

当比较函数不一致时会发生此问题,因此x <y并不总是暗示y <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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 不是错误,但如果异常是"InconsistentComparisionMethodException"而不是标准的空指针ex,那将会很好.当数组中没有空值时......非常混乱 (2认同)

Mar*_*ell 5

你确定问题不是这样SomePropnull

特别是对于字符串或Nullable<T>值。

对于字符串,最好使用:

list.Sort((x, y) => string.Compare(x.SomeProp, y.SomeProp));
Run Code Online (Sandbox Code Playgroud)

(编辑)

对于空安全包装器,您可以使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

我也遇到过这个问题(空引用被传递给我的自定义 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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