5

I have an object called Page, with an instance called p that has a custom property called AssociatedAttributes. If I do the following:

int numMatchingAttributes = p.AssociatedAttributes.Intersect( p.AssociatedAttributes ).Distinct( ).Count( ); 

numMatchingAttributes ends up equaling 0 even though p has 6 AssociatedAttributes. Why does it not equal 6?

AssociatedAttributes is of Type List<Attribute> (Attribute is my own class, not System.Attribute) and Attribute implements IComparable<Attribute>, I did not have it implement IEquatable, should I?

This is the implementation of CompareTo in Attribute:

public int CompareTo(Attribute other)
{
    return Id.CompareTo(other.Id);
}

Id is of type Guid.

this is the AssociatedAttributes property on Page:

public List<Attribute> AssociatedAttributes
        {
        get
            {
            List<Attribute> list = new List<Attribute>( );
            using (
                PredictiveRecommendor dbContext =
                    new PredictiveRecommendor()){
                if ( dbContext != null )
                    { 
                    IQueryable<Attribute> query = from a in dbContext.PageAttributes
                                                  where a.Page.Id.Equals(this.Id)
                                                  select a.Attribute;
                    list = query.ToList(); 


                    }
                }
            return list;
            }
        }

(dbContext is a Telerik OpenAccess context)

Update: Here is what ended up working: In Page is the following method:

public int numberOfIntersectedAssociatedAttributes ( Page other )
        {
        using ( PredictiveRecommendor dbContext = new PredictiveRecommendor( ) )
            {
            IQueryable<Attribute> thisAssocAttributes = from a in dbContext.PageAttributes
                                                        where a.Page.Id.Equals( this.Id )
                                                        select a.Attribute;
            IQueryable<Attribute> otherAssocAttributes = from a in dbContext.PageAttributes
                                                         where a.Page.Id.Equals( other.Id )
                                                         select a.Attribute;
            IQueryable<Attribute> interSection = thisAssocAttributes.Intersect( otherAssocAttributes );

            return interSection.ToList( ).Count;
            }
        }

It's ugly, but it works.

cray1
  • 93
  • 5

1 Answers1

8

Consider the following implementation of Page:

public class Page
{
    public List<Attribute> AssociatedAttributes
    {
        get
        {
            return new List<Attribute>() { 
                new Attribute { Value = "a" }, 
                new Attribute { Value = "b" }, 
                new Attribute { Value = "c" }, 
            };
        }
    }
}

Here the AssociatedAttributes property is returning a different list each time AssociatedAttributes is called. Additionally, the actual items in the list are constructed each time the property is called, it's not just returning references to the same exact Attribute objects. Since Attribute (I assume) does not override Equals or GetHashCode, it will use the default object implementation, which considers objects equal if and only if they are referencing the same object. Since the objects in your two lists aren't referencing the same objects, none of them are equal to each other, even though they may have the same values internally.

The fact that Attribute implements IComparable is irrelivant.

There are several possible things that could be done to change the behavior:

  1. Rather than constructing new objects in the property getter, return references to the same actual objects. This would likely mean having a private backing field for the property, constructing the objects once, and then returning references to the same list.

  2. Override Equals and GetHashCode in Attribute to be dependent on its values, rather than its reference. Convention would dictate that you only do this if the object is immutable, as dealing with objects that have mutating GetHashCode values is...difficult. You could implement IEquatable in addition to doing this, if you wanted, to provide a statically typed Equals method. Note that it's vital to override GetHashCode if you implement IEquatable, if you still want it to be useful.

  3. Create a new object that implements IEqualityComparer<Attribute>. This would be an object external to Attribute that knows how to compare them for equality based on something other than the Equals and GetHashCode implementation of the object itself. Provide an instance of such a type to Intersect and Distinct. (They each have overloads for custom equality comparers.)

Servy
  • 202,030
  • 26
  • 332
  • 449
  • hmm, but I need actual references to already existing attribute objects. I can't just generate new ones. That may work for just the purpose of getting the count of the intersection, but would not work for much else being that they're newly constructed objects. I only did the intersect of the page against itself as a test to see if I am coding things right. – cray1 Aug 21 '13 at 19:31
  • @user1911333 I'm not saying you should do this, I'm saying I think you are already doing this (or something with a similar effect) as an explanation for your observed behavior. If you answered [my question](http://stackoverflow.com/questions/18365587/can-someone-explain-linqs-intersect-properly-to-me-i-dont-understand-why-this#comment26965710_18365587) about how the property is implemented, I may not need to guess. I'm not saying your code *should* look like this; in fact it probably shouldn't. If it did, it would be quite...unusual. – Servy Aug 21 '13 at 19:33
  • Now that you've added the implementation I can concretely say that you are indeed constructing new object instances within the getter, and that is your problem. Options 1 or 3 are likely the best bet for you. An ORM object is likely mutable, and also just generally wouldn't be expected to have overridden `Equals` or `GetHashCode` methods. – Servy Aug 21 '13 at 19:38
  • ah ok that makes a lot more sense. I will try option 1 and see if it works, thanks. – cray1 Aug 21 '13 at 19:42
  • @user1911333 your implementation of `AssociatedAttributes` is something exactly what servy posted in his answer. If you still want to keep that implementation, you may have to implement the `IEqualityComparer` not `IComparable`. – King King Aug 21 '13 at 19:48
  • @Servy, Thank you for your answer. I got it working by simply creating another method in Page that takes in another page, runs two separate queries to get each page's associated attributes that are identical to the query in Associated Attributes except they don't call ToList(). And then running a third query to get the interesection of the two. Kind of a roundabout way I know, but it worked. – cray1 Aug 21 '13 at 20:04