代码之家  ›  专栏  ›  技术社区  ›  cbp

list.sort in c:使用空对象调用比较器

  •  19
  • cbp  · 技术社区  · 16 年前

    使用内置的c_list.sort函数和自定义比较器,我得到了奇怪的行为。

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

    我的比较器类如下:

    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;
        }
    }
    

    这允许将委托传递给列表。排序方法如下:

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

    因此,上面的委托将为 X 参数,即使没有元素 我的名单 是空的。

    更新: 是的,我确信这是参数 X 引发空引用异常!

    更新: 我没有使用框架的list.sort方法,而是尝试了一种自定义排序方法(即 新建BubbleSort()。排序(MyList) )问题就消失了。正如我怀疑的那样,list.sort方法出于某种原因将空值传递给比较器。

    7 回复  |  直到 13 年前
        1
  •  22
  •   Lee    15 年前

    当比较函数不一致时会出现这个问题,这样x<y并不总是意味着y<x。在您的示例中,您应该检查someprop类型的两个实例是如何进行比较的。

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

    我不会在排序函数中将其称为bug——这只是一个要求比较函数是一致的。

    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);
        }
    }
    
        2
  •  3
  •   Marc Gravell    16 年前

    你确定问题不是那个吗 SomeProp null ?

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

    对于字符串,最好使用:

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

    (编辑)

    对于空安全包装,可以使用 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);
        }
    }
    
        3
  •  3
  •   sirobee    13 年前

    我也遇到了这个问题(将空引用传递给我的自定义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,因此Trparse总是失败的。

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

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

        4
  •  0
  •   Matthew Flaschen    16 年前

    马克的回答很有用。我同意他的观点,nullReference是由于调用了一个空属性上的compareto。不需要扩展类,您可以执行以下操作:

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

    其中someprop type是someprop的类型

        5
  •  0
  •   abelenky    16 年前

    出于调试目的,您希望方法是空安全的。(或者至少捕获空引用异常,并以某种硬编码方式处理它)。然后,使用调试器来观察比较其他值的顺序以及调用的成功或失败。

    然后,您将找到您的答案,然后您可以删除空安全。

        6
  •  0
  •   JP Alioto    16 年前

    你能运行这个代码吗?

    mylst.Sort((i, j) =>
                  {
                      Debug.Assert(i.SomeProp != null && j.SomeProp != null);
                      return i.SomeProp.CompareTo(j.SomeProp);
                  }
             );
    
        7
  •  0
  •   Derek Slager    15 年前

    我自己偶然发现了这个问题,发现它与 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));
        }
    
    }