How can I make my generic comparer (IComparer) handle nulls?
Asked Answered
S

4

11

I'm trying to write a generic object comparer for sorting, but I have noticed it does not handle the instance where one of the values it's comparing is null. When an object is null, I want it to treat it the same as the empty string. I've tried setting the null values to String.Empty but then I get an error of "Object must be of type String" when calling CompareTo() on it.

public int Compare(T x, T y)
{
    PropertyInfo propertyInfo = typeof(T).GetProperty(sortExpression);
    IComparable obj1 = (IComparable)propertyInfo.GetValue(x, null);
    IComparable obj2 = (IComparable)propertyInfo.GetValue(y, null);

    if (obj1 == null) obj1 = String.Empty; // This doesn't work!
    if (obj2 == null) obj2 = String.Empty; // This doesn't work!

    if (SortDirection == SortDirection.Ascending)
        return obj1.CompareTo(obj2);
    else
        return obj2.CompareTo(obj1);
}

I'm pretty stuck with this now! Any help would be appreciated.

Superannuated answered 31/1, 2011 at 14:58 Comment(0)
M
28

You cannot treat your T as an empty string unless your T was effectively constrained to being a string. What you should do is have a plan for comparing nulls. Such as

if (obj1 == null && obj2 == null)
   return 0;
else if (obj1 == null)
   return -1;
else if (obj2 == null)
   return 1;
else 
   return obj1.CompareTo(obj2);
Microbiology answered 31/1, 2011 at 15:18 Comment(1)
Thanks Anthony, that works a treat! I don't really know why I never seem to spot the simple solution...Superannuated
C
4
if (SortDirection == SortDirection.Ascending)
    return Comparer<T>.Default.Compare(obj1, obj2);
else
    return Comparer<T>.Default.Compare(obj2, obj1);
Cornflakes answered 31/1, 2011 at 15:27 Comment(0)
B
0

Since T is a generic type, you cannot assign it a String value; you can only assign it a value of type T. If you are only going to use this to compare strings, use String instead of T. Otherwise, add null checking and decide where in order null should fall.

Bozovich answered 31/1, 2011 at 15:5 Comment(2)
That's what I was trying to do (the latter) but I just can't get anything to work. I don't know how to declare a new empty valid T object for it to compare to instead of the null. I'm still confused by generics to be honest!Superannuated
@Nick, a default T to for a reference type would still be null. An instantiated but otherwise empty T could be obtained if T had a where T : new() constraint, which would allow you to instantiate a T inside your method, but it would also mean that all T candidates would have to have a public parameterless constructor. In this particular situation, you shouldn't make this a requirement as you should be able to deal effectively with nulls in your code.Microbiology
M
0
IComparable obj1 = (IComparable)propertyInfo.GetValue(x, null) ?? "";
IComparable obj2 = (IComparable)propertyInfo.GetValue(y, null) ?? "";

This basically means that obj1 will now be the value of propertyInfo.GetValue(x, null) or, if that happens to be null, obj1 will be "".

Or if the problem is that the GetValue crashes on null you could do something like:

IComparable obj1 = "";
try { obj1 = (IComparable)propertyInfo.GetValue(x, null); } catch {}
Mud answered 31/1, 2011 at 15:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.