Reputation: 1551
I have a list of objects, some of which can be null. I want it sorted by some property, with null
s at the end of list. However, List<T>.Sort()
method seems to put null
s in the beginning no matter what the comparer returns. This is a little program that I used to test this:
class Program
{
class Foo {
public int Bar;
public Foo(int bar)
{
Bar = bar;
}
}
static void Main(string[] args)
{
List<Foo> list = new List<Foo>{null, new Foo(1), new Foo(3), null, new Foo(100)};
foreach (var foo in list)
{
Console.WriteLine("Foo: {0}", foo==null?"NULL":foo.Bar.ToString());
}
Console.WriteLine("Sorting:");
list.Sort(new Comparer());
foreach (var foo in list)
{
Console.WriteLine("Foo: {0}", foo == null ? "NULL" : foo.Bar.ToString());
}
Console.ReadKey();
}
class Comparer:IComparer<Foo>
{
#region Implementation of IComparer<in Foo>
public int Compare(Foo x, Foo y)
{
int xbar = x == null ? int.MinValue : x.Bar;
int ybar = y == null ? int.MinValue : y.Bar;
return ybar - xbar;
}
#endregion
}
}
Try this yourself: sorted list is printed as
Foo: NULL
Foo: NULL
Foo: 100
Foo: 3
Foo: 1
null
s Are first, even though they're compared as int.Minvalue
. Is the method buggy or what?
Upvotes: 3
Views: 2846
Reputation: 660128
Your implementation is incorrect because you failed to write the specification first, then write code that meets the specification.
So, start over. Write a specification:
And now you can write code that you are guaranteed matches the spec:
// FooCompare takes two references to Foo, x and y. Either may be null.
// +1 means x is larger, -1 means y is larger, 0 means they are the same.
int FooCompare(Foo x, Foo y)
{
// if x and y are both null, they are equal.
if (x == null && y == null) return 0;
// if one is null and the other one is not then the non-null one is larger.
if (x != null && y == null) return 1;
if (y != null && x == null) return -1;
// if neither are null then the comparison is
// based on the value of the Bar property.
var xbar = x.Bar; // only calculate x.Bar once
var ybar = y.Bar; // only calculate y.Bar once
if (xbar > ybar) return 1;
if (ybar > xbar) return -1;
return 0;
}
// The original poster evidently wishes the list to be sorted from
// largest to smallest, where null is the smallest.
// Reverse the polarity of the neutron flow:
int Compare(Foo x, Foo y)
{
return FooCompare(y, x);
}
Don't mess around with clever integer arithmetic tricks; as you've seen, clever equals buggy. Correctness first.
Upvotes: 13
Reputation: 545598
Let’s say x
is null
and y
is 1.
int xbar = x == null ? int.MinValue : x.Bar;
int ybar = y == null ? int.MinValue : y.Bar;
return ybar - xbar;
So now we have 1 - int.MinValue
. This will be an overflow, so the end result will be negative, hence the null
will be considered less than 1.
For that reason, the computation is fundamentally flawed.
public int Compare(Foo x, Foo y)
{
int xbar = x == null ? int.MinValue : x.Bar;
int ybar = y == null ? int.MinValue : y.Bar;
return ybar.CompareTo(xbar);
}
(Thanks to the commenters for pointing out the flaw in my method.)
Upvotes: 12
Reputation: 17415
Replace this line:
return ybar - xbar;
with this line:
return ybar.CompareTo(xbar);
Because your arithmetic is overflowing.
Upvotes: 4
Reputation: 63338
Your implementation of IComparer.Compare
is unnecessarily complex. Remember that all that matters about the return value is the sign - the magnitude is unimportant. To have null
s compare less than all real Foo
s, simply defer to Int32
s implementation of CompareTo
, after making your substitution:
return (x == null ? int.MinValue : x.Bar)
.CompareTo((y == null ? int.MinValue : y.Bar))
Upvotes: 2
Reputation: 1709
int.MinValue is -2147483648
.
Now, suppose you were comparing 3
and -2147483648
via subtraction, specifically 3 - (-2147483648
?
Also you should consider using the null coalescing operator ??
Upvotes: 0