3

Say I have the object, where the following 3 properties (others have been omitted) constitute a "unique" Plan object (if these are equal to the same values in another Plan object).

public class Plan
{
    public int ID { get; set; }
    public Plan Parent { get; set; }
    public ID SomeOtherProperty { get; set; }
}

Here's my Join code, where I omitted the anonymous method for the Join operators (I know by default this code won't work):

oldPlans
    .Join(newPlans, o => o, n => n, (o, n) => new { Old = o, New = n })
    .ForEach(e =>
    {
        ...
    });

I want to perform a C# Join on two collections of Plan objects. I understand one way is to use an anonymous method for the joining properties, writing out these three properties.

But is there a different method? Can I override GetHashCode? When I tried this, it didn't seem to be calling it that method. I also tried overriding Equals but it didn't seem to be calling that either. Should I override the == and != operators? Can I explicitly call .GetHashCode() for the key selector fields (assuming I overrode it)?

Is it possible to have this Join check the equality of these two objects without complicating the key selectors? Thanks.

PiotrWolkowski
  • 8,408
  • 6
  • 48
  • 68
Ryan Peters
  • 7,608
  • 8
  • 41
  • 57
  • You dont have to use an anonymous object. You simply need to create a `Func resultSelector` which will tell `Enumerable.Join` how to select a given value based on two keys. – Yuval Itzchakov Jan 05 '15 at 12:24

1 Answers1

1

Your code works fine for me - tracking through ReferenceSource, the default comparison is ultimately uses is an ObjectEqualityComparer which calls Equals(), so your idea is correct.

So it comes down to how you implement Equals and GetHashCode. You should override both if you override one, as MSDN states:

CAUTION: If you override the GetHashCode method, you should also override Equals, and vice versa. If your overridden Equals method returns true when two objects are tested for equality, your overridden GetHashCode method must return the same value for the two objects.

Note your ID class will also need to handle both these methods correctly as it should be used by Plan to check for equality and getting the hashcode.

This program worked for me and only prints the second entry with ID=2 (note that I made SomeOtherProperty and int for simplicity but this doesn't affect the approach or code):

class Program
{
    public class Plan
    {
        public int ID { get; set; }
        public Plan Parent { get; set; }
        public int SomeOtherProperty { get; set; }

        // added to show we don't care about this
        public string IgnoreMe { get; set; }

        public Plan(int id, int other, Plan parent, string ignore)
        {
            this.ID = id;
            this.SomeOtherProperty = other;
            this.Parent = parent;
            this.IgnoreMe = ignore;
        }

        public override bool Equals(object obj)
        {
            Plan other = (Plan)obj;
            // just check the relevant properties
            return this.ID == other.ID
                && this.SomeOtherProperty == other.SomeOtherProperty
                && this.Parent == other.Parent;

            // .. or alternatively
            //return (new { ID, SomeOtherProperty, Parent })
            //    .Equals(new { other.ID, other.SomeOtherProperty, other.Parent });
        }

        // nicked from http://stackoverflow.com/a/4630550/1901857
        public override int GetHashCode()
        {
            return new { ID, SomeOtherProperty, Parent }.GetHashCode();
        }

        // just to help debug
        public override string ToString()
        {
            return string.Format("[ID: {0}, Other:{1}, Parent:{2}]", ID, SomeOtherProperty, Parent);
        }
    }

    static void Main(string[] args)
    {
        var parentPlans = new Plan[] {
            new Plan(101, 2, null, "parent1"),
            new Plan(102, 3, null, "parent2"),
            new Plan(103, 4, null, "parent3"),
            new Plan(104, 5, null, "parent4")
        };

        List<Plan> oldPlans = new List<Plan>(new Plan[] {
            new Plan(1, 2, parentPlans[0], "old1"),
            new Plan(2, 3, parentPlans[1], "old2"),
            new Plan(3, 4, parentPlans[2], "old3"),
            new Plan(4, 5, parentPlans[3], "old4")
        });

        List<Plan> newPlans = new List<Plan>(new Plan[] {
            new Plan(11, 2, parentPlans[0], "new1"), // different ID
            new Plan(2, 3, parentPlans[1], "new2"),  // same
            new Plan(3, 14, parentPlans[2], "new3"), // different other ID
            new Plan(4, 5, parentPlans[2], "new4")   // different parent
        });

        foreach (var e in
            oldPlans.Join(newPlans, o => o, n => n, (o, n) => new { Old = o, New = n }))
        {
            Console.WriteLine(e.Old + " / " + e.New);
        };
    }
}

If you think your implementations of Equals and GetHashCode should have worked, then please post them in the question, maybe they are not quite right.

Rhumborl
  • 16,349
  • 4
  • 39
  • 45
  • I was not implementing BOTH Equals and GetHashCode, but one, then tried the other (removing the other). I'll try both and see if it works. – Ryan Peters Jan 05 '15 at 13:57
  • 1
    That was the answer. I HAVE to implement both Equals and GetHashCode (I didn't realize a warning it was throwing earlier indicating this). – Ryan Peters Jan 05 '15 at 15:55