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:
-
Neither
Comparer<T>
norIComparer<T>
pose any type restrictions onT
. So I don’t see any need for imposing anIComparable
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
) -
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 useIEquatable<T>
orEqualityComparer<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)