3
\$\begingroup\$

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

\$\endgroup\$
  • 2
    \$\begingroup\$ mhmm... when I'm looking at the test cases this doesn't seem to be very useful. What is the real purpose of these exctensions? ;-] \$\endgroup\$ – t3chb0t Sep 1 '17 at 6:45
  • 2
    \$\begingroup\$ Its a standard DB function: NullIf() in Sql Server. Google turns up some examples. It more for string processing although i wanted a typesafe enum version. \$\endgroup\$ – crokusek Sep 2 '17 at 0:06
  • \$\begingroup\$ Oh, you're right! I've been using it myself in sql queries many times where it's quite useful - I totaly missed that one, lol, sorry ;-) I cannot however think of any example where I would need it in C#. Usually you don't want nulls. But I guess you have such a case, haven't you? \$\endgroup\$ – t3chb0t Sep 2 '17 at 8:54
  • \$\begingroup\$ I find this function always a little bit counter-intuitive as actually it should be called Null-If-Equal. With this name I think there would be less confusion about its purpose or even better First-Or-Null-If-Equal. \$\endgroup\$ – t3chb0t Sep 2 '17 at 8:57
  • \$\begingroup\$ Please see What to do when someone answers. I have rolled back Rev 4 → 3. \$\endgroup\$ – 200_success Sep 2 '17 at 11:29
2
\$\begingroup\$

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.

| improve this answer | |
\$\endgroup\$
  • \$\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 Sep 1 '17 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 Sep 2 '17 at 0:24
  • \$\begingroup\$ @crokusek: answer updated \$\endgroup\$ – ChrisWue Sep 3 '17 at 22:05
1
\$\begingroup\$

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) 
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.