适当的方式加入多个属性对象集合?Proper way to JOIN multiple properties on object collection?

- 此内容更新于:2015-01-06
主题:

原文:

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.

Yuval Itzchakov的回复:你不需要使用一个匿名对象。您只需要创建一个Func<鼓吹者,锡矿工,TResult>resultSelector告诉枚举。加入如何选择一个给定的值基于两个键。

(原文:You dont have to use an anonymous object. You simply need to create a Func<TOuter, TInner, TResult> resultSelector which will tell Enumerable.Join how to select a given value based on two keys.)

解决方案:
原文:

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.

Ryan Peters的回复:我既没有实现Equals和GetHashCode方法,但一个,然后尝试其他(删除)。我# 39;我试一试,看看它的工作原理。

(原文: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的回复:这是答案。我必须实现Equals和GetHashCode方法(我没有# 39;t实现警告指示早些时候扔这)。

(原文:That was the answer. I HAVE to implement both Equals and GetHashCode (I didn't realize a warning it was throwing earlier indicating this).)