-1
  1. I have this class

    class Border
    {
       int top;
       int bottom;
       int left;
       int right;
    }
    
  2. And I have a Dictionary with Border as a key.

I want to have the same key for this class, if these two values are equal. How can I do it?

J0HN
  • 26,063
  • 5
  • 54
  • 85
Mayo
  • 859
  • 2
  • 10
  • 14
  • 10
    I cannot understand your question – David Heffernan Jan 27 '14 at 12:43
  • rephrase "**I want to have the same key for this class if this values equals.**" – Deepak Ingole Jan 27 '14 at 12:44
  • 1
    If you mean duplicate keys, that is not allowed. If that is what you need, then you don't need a Dictionary. – DonBoitnott Jan 27 '14 at 12:44
  • 3
    Are you just asking how to override `.Equals()`? – David Jan 27 '14 at 12:44
  • You want your Dictionary to hold duplicate keys? – Zaid Amir Jan 27 '14 at 12:45
  • Do you want to check if there is another 'KeyValuePair' that has the same value and when this is true, then don't add the new value to your dictionary? – Loetn Jan 27 '14 at 12:46
  • @DonBoitnott sure it is, you just redefine what "duplicate" means in the given context. I'm not sure if that's what they are asking about, or indeed the exact opposite. – Jon Hanna Jan 27 '14 at 12:47
  • @JonHanna `Dictionary` enforces unique keys. It's not up to anyone but the framework to define duplicate. Unless you are suggesting that OP roll their own `Dictionary` class? – DonBoitnott Jan 27 '14 at 13:03
  • @DonBoitnott Dictionary enforces unique keys by the definition of uniqueness it is created to use. This is why its constructor takes an argument of type `IEqualityComparer` (though defaulting to `EqualityComparer.Default`). Dictionaries would be very limited otherwise (e.g. imagine if we couldn't have case-insensitive string keys). – Jon Hanna Jan 27 '14 at 13:15
  • Some good French documentation to understand Equals and GetHashCode : http://www.olivettom.com/?p=541 – Thomas Jan 27 '14 at 13:29

2 Answers2

4

So, if these need to go as keys in a dictionary, then your class needs to be immutable. Then it's a case of adding equality and a hashcode implementation (thankyou resharper).

public class Border
{
    private readonly int bottom;
    private readonly int left;
    private readonly int right;
    private readonly int top;

    public Border(int top, int left, int bottom, int right)
    {
        this.top = top;
        this.left = left;
        this.bottom = bottom;
        this.right = right;
    }

    protected bool Equals(Border other)
    {
        return bottom == other.bottom && left == other.left && right == other.right && top == other.top;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((Border) obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var hashCode = bottom;
            hashCode = (hashCode*397) ^ left;
            hashCode = (hashCode*397) ^ right;
            hashCode = (hashCode*397) ^ top;
            return hashCode;
        }
    }

    public static bool operator ==(Border left, Border right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(Border left, Border right)
    {
        return !Equals(left, right);
    }

    public int Top
    {
        get { return top; }
    }

    public int Bottom
    {
        get { return bottom; }
    }

    public int Left
    {
        get { return left; }
    }

    public int Right
    {
        get { return right; }
    }
}
Community
  • 1
  • 1
spender
  • 117,338
  • 33
  • 229
  • 351
  • Your `Equals` override tests type, non-nullity and border-specific equality in three separate steps; `return Equals(obj as Border)` would do all three. – Jon Hanna Jan 27 '14 at 12:50
  • Resharper's override... Suggest you address your concerns to JetBrainz ;) – spender Jan 27 '14 at 12:52
  • Why you did not implement IComparable? – Dannydust Jan 27 '14 at 12:54
  • For brevity? Is it necessary? – spender Jan 27 '14 at 12:54
  • It will throw a nullRef exception because `Equals(Border b)` does not check for nullity. – Thomas Jan 27 '14 at 12:54
  • @Thomas: Go for it... http://confluence.jetbrains.com/display/ReSharper/ReSharper+Issue+Tracker – spender Jan 27 '14 at 12:56
  • I was just interested I thought it is a good practice... – Dannydust Jan 27 '14 at 12:57
  • @spender, I was answering to Jon Hanna ;-) – Thomas Jan 27 '14 at 12:57
  • I don't use Resharper, so if it's producing lousy code that's not my problem (though if it's just intended as a starting point fair enough). The weird thing is that the only reason I can think of for using `obj.GetType() != this.GetType()` rather than `obj is Border` is to avoid CA1800 warnings from tools like FxCop. – Jon Hanna Jan 27 '14 at 13:06
  • I'd say that "lousy" is a bit strong... – spender Jan 27 '14 at 13:07
  • If it's in three lines and from a tool that is meant to help one improve quality, I'd have a much tougher line for "lousy" than otherwise. It's fine but improvable as hand-written. It's better than the auto-gen SharpDevelop would give, but I thought ReSharper was supposed to remove code smells and aid refactoring. By that standard, it's lousy. – Jon Hanna Jan 27 '14 at 13:10
  • @JonHanna http://msdn.microsoft.com/en-us/library/336aedhh%28v=vs.85%29.aspx See the first large code sample and commentary under it. `GetType() == other.GetType()` is often preferable in `Equals()` implementations because it checks that the two objects have the same runtime type. Objects should generally not be considered equal if they have the same effective values but different types. – JLRishe Jan 27 '14 at 13:39
  • @jlrishe, then that should be considered in the other equals overload. Indeed, you've pointed out a bug; either the normal case you describe holds, in which case the other equals overload is buggy for not catching it, or there is a reason for allowing an exception to that guideline, so the second overload is buggy for the opposite reason, or else the class should be sealed to make that case impossible. Whichever of the three is the correct case, Equals(other as Border) would be the correct overload, bar the case where some instances should be equal to null (very rarely correct). – Jon Hanna Jan 27 '14 at 13:51
  • @Dannydust Implementing `IComparable` would not make much sense here, but implementing `IEquatable` would be worthwhile and only require another 18 characters. :) http://stackoverflow.com/questions/2734914/whats-the-difference-between-iequatable-and-just-overriding-object-equals – JLRishe Jan 27 '14 at 13:52
  • @JonHanna It's probably worth noting that R# gives you three different options concerning comparisons with subclasses (comparand type check: "exactly the same type as this" OR "exactly of type Border" OR "Equal or subtype of Border") and will trot out different implementations dependent on what you choose. FWIW, I chose "exactly the same type as this". – spender Jan 27 '14 at 13:52
  • @JLRishe: Uh, mixed it up. In fact I was meaning IEquatable. Thx for pointing it out. – Dannydust Jan 27 '14 at 13:56
  • I like that choice in a tool, though the overload accepting border arguments is buggy by that requirement. Resharper does seem less crazy with that pointed out though. Would it produce the same code if the class was sealed? – Jon Hanna Jan 27 '14 at 13:56
  • @JLRishe, always worth doing; it makes the default comparer that will actually call these methods for the dictionary faster, and can catch some silly typo-level errors. – Jon Hanna Jan 27 '14 at 13:58
  • @JLRishe actually, thinking about there's three viable configurations, either the equatable class must be sealed (my normal preference) or it must force descendants to be unequal (with GetType() == other.GetType() in the more strongly typed override), or in rare cases it must seal `Equals` and `GetHashCode` to force the descendants to be equal by the same rule. It occurs to me that in a handful of cases where I've had a sensibly inheritable equality, I haven't sealed those methods to prevent bugs, so thanks for pointing me to that insight. – Jon Hanna Jan 27 '14 at 14:18
0

I'm not sure whether you mean one of two opposite things.

If you mean that you want to make sure two different instances of border are counted as being the same key if their values are the same then you need to redefine equality.

Step 1. Implement IEquatable<Border>, while you don't need to do so, it makes some things faster to run and other things simpler to code.

Step 2. Add your code to implement the Equals method this requires:

public bool Equals(Border other)
{
  return other != null
    && other.bottom == bottom
    && other.top == top
    && other.left == left
    && other.right == right;
}

Edit: As JLRishe points out in a comment on another answer, if this class isn't sealed then the above method should check GetType() == other.GetType() after the null check, unless it has a very good reason not to).

Step 3. Add an override for object.Equals and object.GetHashCode:

public override bool Equals(object other)
{
  return Equals(other as Border);
}
public override int GetHashCode()
{
   //This is a simple method without very good distribution, that will serve for many cases, but can be improved upon.
   return (bottom << 24 | bottom >> 8)
     ^ (top << 16 | top >> 16)
     ^ (left << 8 | left >> 8)
     ^ right;
}

Now the default behaviour of your class when used in a dictionary will be to treat those Border objects with the same bottom, top, left and right as being the same thing.

Alternatively, maybe you mean you've already got that, and you want to have two equal objects treated as differently for some reason (as if they didn't have a specific sense of equivalence defined for them). You can't do that in pure C# without reflection emit, so I wrote http://www.nuget.org/packages/AisA/ for the few times it's useful.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251