3

I want to write an equality comparer for Nullable structs. Lets say, DateTime?. So I come up with this code:

public class NullableEntityComparer<TEntity, TType> : IEqualityComparer<TEntity> 
        where TType : struct
        where TEntity : Nullable<TType>
{
    public bool Equals(TEntity x, TEntity y)
    {
        if(!x.HasValue && ! y.HasValue) return true;
        if(x.HasValue && y.HasValue) return x.Value == y.Value;
        return false;
    }

    public int GetHashCode(TEntity obj)
    {
        if (obj == null) throw new ArgumentNullException("obj");
        if (obj.HasValue) return obj.Value.GetHashCode();
        else return obj.GetHashCode();
    }
}

The compiler doesn't like this and tells me:

'TType?' is not a valid constraint. A type used as a constraint must be an interface, a non-sealed class or a type parameter.   

This is a clear message, however Nullable<T> is a class, and TType? is just a shorthand for Nullable<TType>. Or am I missing something?

Why does this not work? And is there a solution to have an IEqualityComparer<T> use the T.HasValue property?

  • 3
    "however `Nullable` is a class" -- Huh? It's a `struct`. –  Jul 29 '15 at 10:12
  • from msdn: `where T: struct -> The type argument must be a value type. Any value type except Nullable can be specified.` More at https://msdn.microsoft.com/en-us/library/d5x73970.aspx – Amit Kumar Ghosh Jul 29 '15 at 10:24

1 Answers1

5

It's quite simple - Nullable<> is a struct, so it counts as a sealed class, which is prohibited in a constraint (obviously - if you use a sealed class as a constraint, there's no need to use a generic type argument - you already always have exactly the same type).

But you don't need to do this at all. Simply have TType constrained by struct, but instead of using TEntity, just use TType? whenever you need the nullable:

public class NullableEntityComparer<TType> : IEqualityComparer<TType?> 
        where TType : struct
{
    public bool Equals(TType? x, TType? y)
    {
        if(!x.HasValue && ! y.HasValue) return true;
        if(x.HasValue && y.HasValue) return x.Value.Equals(y.Value);
        return false;
    }

    public int GetHashCode(TType? obj)
    {
        return obj.GetHashCode();
    }
}

As a side-note, nullables already have an implementation of equality that includes checking for nulls, so if you can avoid all this if you know the nullable type at compile-time.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • The error message is quite clear: "'TType?' is not a valid constraint. A type used as a constraint must be an interface, a non-sealed class or a type parameter." So indeed, not allowed. Period. – Patrick Hofman Jul 29 '15 at 10:18
  • 1
    Your `if (obj == null)` check is wrong: `null` is a perfectly valid argument when the type is not a reference type, so you shouldn't throw an exception. The [`IEqualityComparer.GetHashCode()`](https://msdn.microsoft.com/en-us/library/ms132155(v=vs.110).aspx) documentation says: "The type of *obj* is a reference type and *obj* is **null**." as the condition for `ArgumentNullException`. Worse yet, it makes your `if (obj.HasValue)` check redundant. (But excellent answer nonetheless.) –  Jul 29 '15 at 10:18
  • 2
    In other words, he needs to abstract over `T`, but not over `Nullable`. – dcastro Jul 29 '15 at 10:18
  • @hvd It's not my check, it's copied over from the OP. I cleaned it up already, though. – Luaan Jul 29 '15 at 10:20
  • great solution, I feel so stupid now. oh well, I'll accept your answer when stackoverflow allows me :) – Jasper Saaltink Jul 29 '15 at 10:20
  • Ah, I overlooked it in the question, thanks for pointing that out. :) –  Jul 29 '15 at 10:21