List.Sort in C#: comparer being called with null object

后端 未结 7 1157
攒了一身酷
攒了一身酷 2021-01-01 11:48

I am getting strange behaviour using the built-in C# List.Sort function with a custom comparer.

For some reason it sometimes calls the comparer class\'s Compare meth

相关标签:
7条回答
  • 2021-01-01 12:19

    This problem will occur when the comparison function is not consistent, such that x < y does not always imply y < x. In your example, you should check how two instances of the type of SomeProp are being compared.

    Here's an example that reproduces the problem. Here, it's caused by the pathological compare function "compareStrings". It's dependent on the initial state of the list: if you change the initial order to "C","B","A", then there is no exception.

    I wouldn't call this a bug in the Sort function - it's simply a requirement that the comparison function is consistent.

    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);
        }
    }
    
    0 讨论(0)
  • 2021-01-01 12:32

    Are you sure the problem isn't that SomeProp is null?

    In particular, with strings or Nullable<T> values.

    With strings, it would be better to use:

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

    (edit)

    For a null-safe wrapper, you can use Comparer<T>.Default - for example, to sort a list by a property:

    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);
        }
    }
    
    0 讨论(0)
  • 2021-01-01 12:35

    I stumbled across this issue myself, and found that it was related to a NaN property in my input. Here's a minimal test case that should produce the exception:

    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));
        }
    
    }
    
    0 讨论(0)
  • 2021-01-01 12:38

    I too have come across this problem (null reference being passed to my custom IComparer implementation) and finally found out that the problem was due to using inconsistent comparison function.

    This was my initial IComparer implementation:

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

    The mistake in this code was that Compare would return -1 whenever one of the values could not be parsed properly (in my case it was due to wrongly formatted string representations of numeric values so TryParse always failed).

    Notice that in case both x and y were formatted incorrectly (and thus TryParse failed on both of them), calling Compare(x, y) and Compare(y, x) would yield the same result: -1. This I think was the main problem. When debugging, Compare() would be passed null string pointer as one of its arguments at some point even though the collection being sorted did not cotain a null string.

    As soon as I had fixed the TryParse issue and ensured consistency of my implementation the problem went away and Compare wasn't being passed null pointers anymore.

    0 讨论(0)
  • 2021-01-01 12:39

    Marc's answer is useful. I agree with him that the NullReference is due to calling CompareTo on a null property. Without needing an extension class, you can do:

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

    where SomePropType is the type of SomeProp

    0 讨论(0)
  • 2021-01-01 12:40

    For debugging purposes, you want your method to be null-safe. (or at least, catch the null-ref. exception, and handle it in some hard-coded way). Then, use the debugger to watch what other values get compared, in what order, and which calls succeed or fail.

    Then you will find your answer, and you can then remove the null-safety.

    0 讨论(0)
提交回复
热议问题