Welcome GuestLogin

A Drunken Wiki

RSS RSS

A Drunken Wiki is the companion wiki to the blog "A Drunken Madman", written by Jason Brown.

Navigation




Search the wiki
»

Vaccination Saves Lives: Stop The Australian Vaccination Network


Find me on Twitter, Facebook, Google+, Pinterest and LinkedIn





Pin It






Alpha Archive Atheism Blue Mountains Climbing Cycling Development DotNet GPS Guitar HMHB JavaScript JQuery LINQ Meta MTB Music Perl Powershell RNP SharePoint Skepticism Social Media Training Ukulele Vaccines Visual Studio WebParts Woodford Festival 2011 XML

PoweredBy
I've been working with LINQ a bit recently, and today I found a quirk I had no idea about and had to search several times and several different ways to eventually resolve. It all revolves around the Distinct() operator and how it compares data.

The background

I was tasked with pulling in some XML and building a page with it. Simple enough. The page was a list of locations, in suburbs, which was drawn from an RSS feed. The title contains the address, the category contains the suburb. The initial task was to order by suburb, but a detail was added.

At the top of the page, we want a list of suburbs featured in the list this week, as anchor links that will jump to the first occurrence of that suburb.

All fine and dandy, I thought, I'll just .Distinct() the LINQ query and we'll have that info. I'll then bind it to a repeater and we're away.

Not so much

The problem

The problem was that Distinct just wasn't working straight out. I had a custom object, AnchorItem, which was being selected out to be databound

var forAnchors = (from item in oFeed.Elements("channel").Elements("item")
                              orderby item.Element("category").Value ascending
                              where item.Element("title").Value.Contains("*NEW*")
                              select new AnchorItem(item.Element("category").Value)).Distinct();

It appeared LINQ was unable to compare these objects in order to determine which was distinct. Here's the object as it originally appeared (trimmed for clarity).

public class AnchorItem 
    {      

        public string AnchorName { get; set; }
        public string AnchorTag { get; set; }
        public AnchorItem(string aName)
        {
            AnchorName = aName;
            AnchorTag = RemoveSpaces(aName);
        }
    }

So I figured, off the top of my head, that what it needed was to implement the IComparible interface, so that LINQ could compare one object to another, and therefore selecte distinct objects.

public class AnchorItem  : IComparable
    {      

        public string AnchorName { get; set; }
        public string AnchorTag { get; set; }
        public AnchorItem(string aName)
        {
            AnchorName = aName;
            AnchorTag = RemoveSpaces(aName);
        }

        public int CompareTo(object o)
        {
            if (o is AnchorItem)
            {
                AnchorItem a2 = (AnchorItem)o;
                return AnchorName.CompareTo(a2.AnchorName);
            }
            else
            {
                throw new ArgumentException("Cannot compare AnchorItem to non AnchorItem");
            }
        }
    }

I should know better than to trust top-of-the-head remembering. I should have gone to the docs. LINQ doesn't care about IComparible. It actually uses IEquatable. And more specifically, it uses the GetHashCode() method to do its comparison. SO here's the code I ended up with for my AnchorItem class

public class AnchorItem  : IComparable, IEquatable<AnchorItem>
    {      

        public string AnchorName { get; set; }
        public string AnchorTag { get; set; }
        public AnchorItem(string aName)
        {
            AnchorName = aName;
            AnchorTag = RemoveSpaces(aName);
        }

        public int CompareTo(object o)
        {
            if (o is AnchorItem)
            {
                AnchorItem a2 = (AnchorItem)o;
                return AnchorName.CompareTo(a2.AnchorName);
            }
            else
            {
                throw new ArgumentException("Cannot compare AnchorItem to non AnchorItem");
            }
        }

        public bool Equals(AnchorItem o)
        {
            return (o.AnchorName == this.AnchorName);
        }

        public override int GetHashCode()
        {
            int hashName = AnchorName == null ? 0 : AnchorName.GetHashCode();
            int hashTag = AnchorTag == null ? 0 : AnchorTag.GetHashCode();
            return hashName ^ hashTag;
        }

        string RemoveSpaces(string inputString)
        {
            return inputString.Replace(" ", String.Empty);
        }
        
    }

At some point, I'll trim it back down to bare minimum, but it works, and now I have this page as an aide memoire

 

ScrewTurn Wiki version 3.0.5.600. Some of the icons created by FamFamFam.