开发者

Union-ing two custom classes returns duplicates

开发者 https://www.devze.com 2023-01-04 19:58 出处:网络
I have two custom classes, ChangeRequest and ChangeRequests, where a ChangeRequests can contain many ChangeRequest instances.

I have two custom classes, ChangeRequest and ChangeRequests, where a ChangeRequests can contain many ChangeRequest instances.

public class ChangeRequests : IXmlSerializable, ICloneable, IEnumerable<ChangeRequest>,
    IEquatable<ChangeRequests> { ... }

public class ChangeRequest : ICloneable, IXmlSerializable, IEquatable<ChangeRequest>
    { ... }

I am trying to do a union of two ChangeRequests instances. However, duplicates do not seem to be removed. My MSTest unit test is as follows:

var cr1 = new ChangeRequest { CRID = "12" };
var crs1 = new ChangeRequests { cr1 };
var crs2 = new ChangeRequests
               {
                   cr1.Clone(),
                   new ChangeRequest { CRID = "34" }
               };
Assert.AreEqual(crs1[0], crs2[0], "First CR in both ChangeRequests should be equal");
var unionedCRs = new ChangeRequests(crs1.Union<ChangeRequest>(crs2));
ChangeRequests expected = crs2.Clone();
Assert.AreEqual(expected, unionedCRs, "Duplicates should be removed from a Union");

The test fails in the last line, and unionedCRs contains two copies of cr1. When I tried to debug and step through each line, I had a breakpoint in ChangeRequest.Equals(object) on the first line, as well as in the first line of ChangeRequest.Equals(ChangeRequest), but neither were hit. Why does the union contain duplicate ChangeRequest instances?

Edit: as requested, here is ChangeRequests.Equals(ChangeRequests):

public bool Equals(ChangeRequests other)
{
    if (ReferenceEquals(this, other))
    {
        return true;
    }

    return null 开发者_如何学运维!= other && this.SequenceEqual<ChangeRequest>(other);
}

And here's ChangeRequests.Equals(object):

public override bool Equals(object obj)
{
    return Equals(obj as ChangeRequests);
}

Edit: I overrode GetHashCode on both ChangeRequest and ChangeRequests but still in my test, if I do IEnumerable<ChangeRequest> unionedCRsIEnum = crs1.Union<ChangeRequest>(crs2);, unionedCRsIEnum ends up with two copies of the ChangeRequest with CRID 12.

Edit: something has to be up with my Equals or GetHashCode implementations somewhere, since Assert.AreEqual(expected, unionedCRs.Distinct(), "Distinct should remove duplicates"); fails, and the string representations of expected and unionedCRs.Distinct() show that unionedCRs.Distinct() definitely has two copies of CR 12.


Make sure your GetHashCode implementation is consistent with your Equals - the Enumerable.Union method does appear to use both.

You should get a warning from the compiler if you've implemented one but not the other; it's still up to you to make sure that both methods agree with each other. Here's a convenient summary of the rules: Why is it important to override GetHashCode when Equals method is overridden?


I don't believe that Assert.AreEqual() examines the contents of the sequence - it compares the sequence objects themselves, which are clearly not equal.

What you want is a SequenceEqual() method, that will actually examine the contents of two sequences. This answer may help you. It's a response to a similar question, that describes how to compare to IEnumerable<> sequences.

You could easily take the responder's answer, and create an extension method to make the calls look more like assertions:

public static class AssertionExt
{
  public static bool AreSequencesEqual<T>( IEnumerable<T> expected, 
                                           IEnumerable<T> sequence )
  {
    Assert.AreEqual(expected.Count(), sequence .Count()); 

    IEnumerator<Token> e1 = expected.GetEnumerator(); 
    IEnumerator<Token> e2 = sequence .GetEnumerator(); 

    while (e1.MoveNext() && e2.MoveNext()) 
    { 
        Assert.AreEqual(e1.Current, e2.Current); 
    }
  }
}

Alternatively you could use SequenceEqual(), to compare the sequences, realizing that it won't provide any information about which elements are not equal.


As LBushkin says, Assert.AreEqual will just call Equals on the sequences.

You can use the SequenceEqual extension method though:

Assert.IsTrue(expected.SequenceEqual(unionedCRs));

That won't give much information if it fails, however.

You may want to use the test code we wrote for MoreLINQ which was sequence-focused - if the sequences aren't equal, it will specify in what way they differ. (I'm trying to get a link to the source file in question, but my network connection is rubbish.)

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号