Generic extension method for NullIf() supporting nullable and non-nullable value types

Posted on

Problem

I would like a simple, modestly efficient NullIf() generic extension for nullable and non-nullable value types including enums. The trickiness seems to be with equality testing in generics.

Any issues with this working implementation?

/// <summary>
/// Return null if the value is equal to the argument.  Applies to value types including enums.
/// </summary>
public static T? NullIf<T>(this T value, T equalsThis) 
    where T : struct, IComparable // values types (including enum)
{
    return Comparer<T>.Default.Compare(value, equalsThis) == 0
        ? (T?)null
        : value;
}

/// <summary>
/// Return null if the value is null or the value is equal to the argument.
/// Applies to value types including enums.
/// </summary>
public static T? NullIf<T>(this T? value, T equalsThis) 
    where T : struct, IComparable  // values types (including enum)
{
    return !value.HasValue
        ? (T?)null
        : value.Value.NullIf(equalsThis);
}

Test cases:

int i = 32;
int? i2 = i.NullIf(32); // null
int? i3 = i.NullIf(50); // 32

System.IO.FileAccess fa = System.IO.FileAccess.Read;
System.IO.FileAccess? fa2 = fa.NullIf(System.IO.FileAccess.Read);       // null
System.IO.FileAccess? fa3 = fa.NullIf(System.IO.FileAccess.ReadWrite);  // Read

References: Comparer<T>.Default

Solution

Just a couple of observations:

  1. Neither Comparer<T> nor IComparer<T> pose any type restrictions on T. So I don’t see any need for imposing an IComparable restriction in the implementation.

    The other option would be to keep the IComparable restriction and make actually use of that interface (i.e. return value.CompareTo(equalsThis) == 0)

  2. IComparable is the wrong abstraction to use. IComparable is meant for objects to provide a sort order as it has the concept of smaller, equal and greater. For your purposes you simply want to test for equality – so you should use IEquatable<T> or EqualityComparer<T> instead.

Update:

Turns out that enums generally don’t implement IEquatable<T>. The best option in that case would probably be to allow the user to optionally pass in an IEqualityComparer<T> defaulting to EqualityComparer<T>.Default and again not impose any additional type restrictions.

Even though it turned out that NullIf is inspired by a similar SQL function NULLIF I think the C# edition should be calles FirstOrNullIfEqual. I always fould the SQL one a little bit counter-intuitive and incomplete becasue every time I see it I’m automatically asking myself null-if what?

As far as the API is concered I think one more overload allowing to specify a custom IEqualityComparer<T> would be useful in many cases:

public static T? NullIf<T>(this T value, T equalsThis, IEqualityComparer<T> comparer) 

Leave a Reply

Your email address will not be published. Required fields are marked *