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
2 Answers 2
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.
-
\$\begingroup\$ The link referenced at the end of question states that the Comparer<T>.Default.Compare() supports IComparable<T> types (yes, not imposed on T, just optional). I verified this to be true in the .NET source code. I plan to convert it over to use EqualityComparer<T> anyway. \$\endgroup\$crokusek– crokusek2017年09月01日 23:56:14 +00:00Commented Sep 1, 2017 at 23:56
-
\$\begingroup\$ Unlike IComparable, enums don't seem to support IEquatable<T> (compile error "there is no boxing conversion") and there is no non-generic IEquatable so I dropped the constraint. \$\endgroup\$crokusek– crokusek2017年09月02日 00:24:34 +00:00Commented Sep 2, 2017 at 0:24
-
\$\begingroup\$ @crokusek: answer updated \$\endgroup\$ChrisWue– ChrisWue2017年09月03日 22:05:40 +00:00Commented Sep 3, 2017 at 22:05
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)
null
s. But I guess you have such a case, haven't you? \$\endgroup\$