views:

1773

answers:

7

Currently I have this (edited after reading advice):

struct Pair<T, K> : IEqualityComparer<Pair<T, K>>
{
    readonly private T _first;
    readonly private K _second;

    public Pair(T first, K second)
    {
        _first = first;
        _second = second;

    }

    public T First { get { return _first; } }
    public K Second { get { return _second; } }

    #region IEqualityComparer<Pair<T,K>> Members

    public bool Equals(Pair<T, K> x, Pair<T, K> y)
    {
        return x.GetHashCode(x) == y.GetHashCode(y);
    }

    public int GetHashCode(Pair<T, K> obj)
    {
        int hashCode = obj.First == null ? 0 : obj._first.GetHashCode();

        hashCode ^= obj.Second == null ? 0 : obj._second.GetHashCode();

        return hashCode;
    }

    #endregion

    public override int GetHashCode()
    {
        return this.GetHashCode(this);
    }

    public override bool Equals(object obj)
    {
        return (obj != null) && 
 (obj is Pair<T, K>) && 
 this.Equals(this, (Pair<T, K>) obj);
    }
}

The problem is that First and Second may not be reference types (VS actually warns me about this), but the code still compiles. Should I cast them (First and Second) to objects before I compare them, or is there a better way to do this?

Edit: Note that I want this struct to support value and reference types (in other words, constraining by class is not a valid solution)

Edit 2: As to what I'm trying to achieve, I want this to work in a Dictionary. Secondly, SRP isn't important to me right now because that isn't really the essence of this problem - it can always be refactored later. Thirdly, comparing to default(T) will not work in lieu of comparing to null - try it.

+2  A: 

Your IEqualityComparer implementation should be a different class (and definately not a struct as you want to reuse the reference).

Also, your hashcode should never be cached, as the default GetHashcode implementation for a struct (which you do not override) will take that member into account.

leppie
A: 

Regarding the warning, you can use default(T) and default(K) instead of null.

I can't see what you're trying to achieve, but you shouldn't be using the hashcode to compare for equality - there is no guarantee that two different objects won't have the same hashcode. Also even though your struct is immutable, the members _first and _second aren't.

Joe
Do all built-in value types have 0 as the hashcode for their default values?
ilitirit
I'd be very surprised if that wasn't the case but wouldn't like to guarantee it.
Joe
Also, default(T) and default(K) doesn't work "Cannot apply '==' to types 'T' and 'T'"
ilitirit
A: 

First of all this code violates SRP principle. Pair class used to hold pairs if items, right? It's incorrect to delegate equality comparing functionality to it.

Next let take a look at your code:

Equals method will fail if one of the arguments is null - no good. Equals uses hash code of Pair class, but take a look at the definition of GetHashCode, it just a combination of pair members hash codes - it's has nothing to do with equality of items. I would expect that Equals method will compare actual data. I'm too busy at the moment to provide correct implementation, unfortunately. But from the first look, you code seems to be wrong. It would be better if you provide us description of what you want to achieve. I'm sure SO members will be able to give you some advices.

aku
Firstly, how can the Equals method accept a null parameter in normal code flow?Secondly, the hashcode *can* be used for equality. In fact, that's what it's used for most often (comparing items). The only problem with this code is that First and Second may not be immutable.
ilitirit
My apologies. Two equal objects share the same hashcode, but two objects with the same hashcode may not be equal.
ilitirit
A: 

Might I suggest the use of Lambda expressions as a parameter ? this would allow you to specify how to compare the internal generic types.

Alexandre Brisebois
A: 

I don't get any warning when compiling about this but I assume you are talking about the == null comparison? A cast seems like it would make this all somewhat cleaner, yes.

PS. You really should use a separate class for the comparer. This class that fills two roles (being a pair and comparing pairs) is plain ugly.

Sander
+1  A: 

If you use hashcodes in comparing methods, you should check for "realy value" if the hash codes are same.

bool result = ( x._hashCode == y._hashCode );
if ( result ) { result = ( x._first == y._first && x._second == y._second ); }
// OR?: if ( result ) { result = object.Equals( x._first, y._first ) && object.Equals( x._second, y._second ); }
// OR?: if ( result ) { result = object.ReferenceEquals( x._first, y._first ) && object.Equals( x._second, y._second ); }
return result;

But there is littlebit problem with comparing "_first" and "_second" fields. By default reference types uses fore equality comparing "object.ReferenceEquals" method, bud they can override them. So the correct solution depends on the "what exactly should do" the your comparing method. Should use "Equals" method of the "_first" & "_second" fields, or object.ReferenceEquals ? Or something more complex?

TcKs
+2  A: 

It looks like you need IEquatable instead:

internal struct Pair<T, K> : IEquatable<Pair<T, K>>
{
  private readonly T _first;
  private readonly K _second;

  public Pair(T first, K second)
  {
    _first = first;
    _second = second;
  }

  public T First
  {
    get { return _first; }
  }

  public K Second
  {
    get { return _second; }
  }

  public bool Equals(Pair<T, K> obj)
  {
    return Equals(obj._first, _first) && Equals(obj._second, _second);
  }

  public override bool Equals(object obj)
  {
    return obj is Pair<T, K> && Equals((Pair<T, K>) obj);
  }

  public override int GetHashCode()
  {
    unchecked
    {
      return (_first != null ? _first.GetHashCode() * 397 : 0) ^ (_second != null ? _second.GetHashCode() : 0);
    }
  }
}
Ilya Ryzhenkov