1

I have two objects that are something like this:

class Container {
    public HashSet<Item> Items { get; }
}

class Item {
    public Container Parent { get; set; }
    public string Value1 { get; set; }
    public int Value2 { get; set; }
}

Every Item instance must belong to a Container instance, and there is a one-to-many relationship between the two that is always kept in sync at both ends.

I am now faced with implementing a method that compares two instances of Item to see if their Value1 and Value2 values match. The method will not take into consideration the Parent value since each pair of instances I compare definitely differ on this value, so doing so would make the method I am implementing useless since it would then have the same result (false) as the object.ReferenceEquals method.

My question is as follows. Should I implement this method as the object's public override bool Equals( object obj ) method (along with GetHashCode)? Or does the fact that it ignores its Parent property preclude me from doing so? Why or why not? An alternative idea I have is to just implement it as public bool EqualsIgnoreParent( Item other ) which does not override anything; I could then call it from a custom comparer.

HappyNomad
  • 4,458
  • 4
  • 36
  • 55
  • 2
    It can get kind of messy when you have value equality on a mutable object that you put into a `HashSet` because to operate properly the hash code must not change once it has been added to the set. – Mike Zboray Aug 06 '13 at 05:03
  • I prefer implementing an `IEqualityComparer` for mutable objects instead of overriding `Equals`. – CodesInChaos Aug 07 '13 at 15:14
  • @CodesInChaos Yup, that's what I did. See my answer below. – HappyNomad Aug 07 '13 at 19:07

5 Answers5

1

Thanks to mikez's comment and Eric Lippert's blog post, I found out that I shouldn't override Item's Equals or GetHashCode methods since Item's instances are mutable and added to a HashSet.

Following the suggestion here, I decided to separate the two concepts of equality.

  1. Identity. I did not override Item's Equals and GetHashCode methods. I left those methods as they are in object. That's what the HashSet will use.
  2. Equivalence. I created a public singleton class called ValueComparer that's nested within Item and implements IEqualityComparer<Item>. This is where I put the comparison logic that I describe in my question.
Community
  • 1
  • 1
HappyNomad
  • 4,458
  • 4
  • 36
  • 55
  • One nice thing about `IEqualityComparer` is that `HashSet`, `Dictionary` etc. have constructor overloads that allow you to use them. – CodesInChaos Aug 07 '13 at 19:14
  • I prefer deriving from `EqualityComparer` instead of implementing `IEqualityComparer` directly. – CodesInChaos Aug 07 '13 at 19:15
  • @CodesInChaos What advantage do you find in `EqualityComparer`? Perhaps if you read [here](http://stackoverflow.com/q/5707347/122781) and [here](http://stackoverflow.com/a/340526/122781) then you'll change your mind. – HappyNomad Aug 07 '13 at 20:45
  • Doesn't gain much, but you get an implementation of the non-generic `IEqualityComparer` interface for free. But it doesn't have significant downsides either. So it's mostly a matter of taste. – CodesInChaos Aug 07 '13 at 21:05
  • Where in the framework can I pass in a non-generic `IEqualityComparer`? – HappyNomad Aug 07 '13 at 21:41
0

I guess you wanted to implement IEquatable. so i suggest you just overloads Euqals(object other) with Euqals(Item other), while overwrites Equals(object other) by calling Equals(Item other).

of course, make GetHashCode return -1 always to force the equality comparison to be done via Equals.here is an explain of why need to overwrite GetHashCode method - Why is it important to override GetHashCode when Equals method is overridden?

Community
  • 1
  • 1
Rex
  • 2,130
  • 11
  • 12
0

Yes, you can implement this with Equals and GetHashCode, as long as Value1 and Value2 don't change or your hash is independent from any change. (otherwise Collections using your HashCode won't work)

Additionally, you could reduce the Item class to the basic members

class Item {
    public string Value1 { get; set; }
    public int Value2 { get; set; }
}

and maintain the relation in an additional dictionary: (which would be efficient, if you implement GetHashCode correctly)

Dictionary<Item, Container> ContainersOfItems;
Thomas B.
  • 2,276
  • 15
  • 24
  • Regarding your second idea, I can't see replacing the `Parent` property with a static dictionary as an improvement. I think the opposite; it would complicate the code without practical benefit. – HappyNomad Aug 06 '13 at 17:19
  • Maintaining lists on both side of the relationship already complicates the code due to redundancy ;) the benefit would be, that an – Thomas B. Aug 06 '13 at 18:50
  • ... unavailable field can't have an effect on any equality logic. Thus gaining safety, losing flexibility, simplicity and readability. Same trade off as in statically typed languages. You could of course hide the static dictionary behind a Parent property. – Thomas B. Aug 06 '13 at 18:59
  • If the bi-directionality is redundant then why do you suggest the static dictionary which would be equally as "redundant"? Obviously, it serves a purpose. All of the fields in my example are potentially "unavailable" (notice the setters are public), not just `parent`. By putting it in a static dictionary, you are just changing the backing store such that a dictionary look-up would unnecessarily be required. – HappyNomad Aug 06 '13 at 19:12
  • I suggested it, because it makes it harder to implement Equals the wrong way. As another answers said: "Don't do it, unless you are 100% sure that the Parent member NEVER makes any difference". Without a "Parent" there won't be any discussion about it. Additionally, regarding object oriented decomposition: If the "value" of an item does not depend on its parent, why including it? – Thomas B. Aug 06 '13 at 19:48
  • Of course, it depends on your style: OOP style, decomposition style, architecture style. Sometimes doubly linked lists, tree nodes with an explicit parent field, or unnormalized database tables make sense. Mostly because of performance reasons: Trading memory for speed, or just for better code readibility in your algorithm. – Thomas B. Aug 06 '13 at 19:49
  • I agree it would make it harder - to implement it any way at all, that is. – HappyNomad Aug 06 '13 at 20:01
0

I would suggest that it might be more meaningful to split Item so it looks something like:

class Container<TContent> {
    public HashSet<Item<TContent>> Items { get; }
}

class Item<TContent> {
    public Container Parent;
    public TContent Content;
}

If you do that, then you can define a type to hold your content which has an Equals method suitable for that type, and initialize your HashSet with an IEqualityComparer<Item<TConcent>> which bases equality upon each Item<TContent>'s Content field.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • I just finished implementing the solution. Modifying the object model would have been heavy handed and wasn't necessary. – HappyNomad Aug 06 '13 at 23:34
-1

Don't do it, unless you are 100% sure that the Parent member NEVER makes any difference. There might be many use cases you don't thought of, (what if you need to put items from 2 containers in a single Dictionary?) so i recommend to stick to the design. If by definition equality is determined only by other members, go for it. Otherwise, not.
You can always write case-specific equality logic later. Remember that Dictionary, List, HashSet and other collecctions allow you to define a custom IEqualityComparer.
There's an another option, but be very careful. Override both methods, but make GetHashCode more tolerant than Equals, that means, make unequal objects have the same hash code, and NOT the opposite. Make GetHashCode ignore the Parent member, but not Equals. it won't harm anything, and then if you want to ignore the Parent member in dictionaries, write an IEqualityComparer that compares them the same way like GetHashCode.

RoadBump
  • 733
  • 7
  • 16
  • 1
    `An option is to override only GetHashCode` You should always either override both equals and gethashcode or neither, never just one. It's important that they both have the same semantic meaning. – Servy Aug 06 '13 at 17:27
  • As i saw dictionary implementations, `GetHashCode` is used to calculate a starting search point within the dictionary. From this point the dict searches the item by invoking `Equals`. Its NOT necassary for the hash code to be unique, as the final equality check is made only by `Equals`. – RoadBump Aug 06 '13 at 21:22
  • Of course not, but it is important for both `Equals` as well as `GetHashCode` to be based on the same concept of equality. If two objects are equal but have a different hash code because the `Equals` method uses value equality and the `GetHashCode` is based on reference equality (or vice versa) the dictionaries break down; it all of a sudden becomes possible to add multiple equal keys, or it will say an item doesn't exist when it does, etc. – Servy Aug 07 '13 at 13:30
  • Not both ways. If 2 different objects has the same hash code it's ok, and does not break dictionaries as i wrote in a previous comment. But the opposite, 2 equal objects that have different hash codes, is very bad. What i suggest is to make the `GetHashCode` more tolerant- that is, 2 different objects with different `Parent`s will have the same hash code (which ignores the `Parent`), and that's ok. – RoadBump Aug 07 '13 at 14:17
  • Yes, I know that, in fact that's exactly what I just told you, but that's not what you're suggesting. By suggesting people override either `GetHashCode` or `Equals` but not the other, you create situations in which two objects are equal according to `Equals` but not equal according to `GetHashCode`. That's very bad, and that's exactly what you are suggesting in your answer. You shouldn't do that. There is even a compiler warning for doing that as it has such a high probability of being incorrect code. – Servy Aug 07 '13 at 14:20
  • Edited to avoid confusion. When you think on this, every time you use a `IEqualityComparer` you can break the dictionary... I think that's a good solution for the OP's specific problem. – RoadBump Aug 07 '13 at 14:47
  • It is still wrong, and bad, for several reasons. First, `Equals` will, if not overridden, use reference equality, not value equality, so overriding `GetHashCode` as you've shown and not overriding `Equals` won't result in the behavior you described. – Servy Aug 07 '13 at 14:51
  • As the OP put it, in his context overriding `Equals` is the same as `ReferenceEquals`. Generally i don't recommend such thing and i wrote it, i'm just trying to solve his private dilemma. – RoadBump Aug 07 '13 at 15:00
  • If you don't override `Equals` the implementation in `object` will be used. It uses reference equality. As I'm telling you, your solution *doesn't* solve his problem. It simply results in a program that won't work, and for very confusing reasons. – Servy Aug 07 '13 at 15:02
  • I don't understand why it won't work, as long that `GetHashCode` is more tolerant. If so, implementing `IEqualityComparer` will not work too. By the way, get a little correction. – RoadBump Aug 07 '13 at 15:14
  • The whole point is that if you only override one, `GetHashCode` **wouldn't be more tolerant**. It would be independent, and as a result wouldn't work. You would need to override both, and as I said before, you should always be overriding both (or nether) in virtually every single situation, including this one. – Servy Aug 07 '13 at 15:25
  • I wrote to make it more tolerant. Please take a look on the edited answer. What i say is, you can make `Equals` differ but it must be less tolerant, just like you do in `IEqualityComparer`. Anyway, thanks for warning about possible confusion. – RoadBump Aug 07 '13 at 15:34