1

I was surprised when I found that my override GetHashCode() method of class Foo is called when I am using foreach to travel in IEnumerable<Foo>. But it doesn't happens in others cases. Why?

Some parts of real code:

var allVolumeImagesInvolvedInMerge = volumeChainsToMerge
    .SelectMany(x => x);

var allVolumeImagesNotInvolvedInMerge = allVolumeImagesWithinCell
    .Except(allVolumeImagesInvolvedInMerge)
    .Where(vi => volumeImagesNotAllowedToDelete.ContainsFast(vi) == false);

var volumeImagesCandidatesForDeletion = allVolumeImagesNotInvolvedInMerge
    .Where(x => driverVolumeIds.Contains(x.DriverVolumeId));

var groupedVolumeImagesCandidatesForDeletion = volumeImagesCandidatesForDeletion
    .GroupBy(vi => vi.DriverVolumeId);

// here GetHashCode is called
foreach (var group in groupedVolumeImagesCandidatesForDeletion)
{
   ...
}
nawfal
  • 70,104
  • 56
  • 326
  • 368
Brans Ds
  • 4,039
  • 35
  • 64
  • 18
    Post your loop and your class – acrilige Apr 03 '13 at 13:46
  • 12
    Put a breakpoint on GetHashCode and then look at the call stack. – Eric Lippert Apr 03 '13 at 13:48
  • 4
    I bet the `IEnumerable` involves [Enumerable.Distinct](http://msdn.microsoft.com/en-us/library/bb348436.aspx) – dtb Apr 03 '13 at 13:49
  • My guess (from looking in reflector) is that the IEnumerable is abstracting a collection (since itself is just an interface) which uses hash codes (maybe for MoveNext()). If you show sample code, maybe you can get a more specific answer. – Elad Lachmi Apr 03 '13 at 13:53
  • @EricLippert: or maybe his IEnumerable is wrapping a dictionary or hashtable. – Elad Lachmi Apr 03 '13 at 13:55
  • @EladLachmi: Merely enumerating an existing hash table does not typically re-hash the entries. Join and distinct build *new* hash tables as they work. – Eric Lippert Apr 03 '13 at 13:57
  • @dtb: Or any other set operation ;p (and tolookup and todictionary) – leppie Apr 03 '13 at 14:00

1 Answers1

7

I assume that your IEnumerable<Foo> is not a collection type like Foo[] or List<Foo> but a linq query which uses deferred execution. So when you use foreach (or ToList, Any etc) you'll execute the query which causes all involved methods to be executed.

Maybe you are using GroupBy, Distinct, Intersect,Except, Join or other methods that use your overridden GetHashCode and Equals(if GetHashCode returns an equal value).

Here's a simple example which reproduces it:

public class Foo
{
    public int ID { get; set; }

    public override int GetHashCode()
    {
        return ID.GetHashCode();
    }
    public override bool Equals(object obj)
    {
        Foo f2 = obj as Foo;
        if (f2 == null) return false;
        return ID == f2.ID;
    }
}

Now this simple linq query demonstrates that GetHashCode is executed in the foreach due to the deferred execution of the Enumerable extension method:

IEnumerable<Foo> list1 = new List<Foo>() { new Foo { ID = 1 }, new Foo { ID = 2 }, new Foo { ID = 3 } };
IEnumerable<Foo> list2 = new List<Foo>() { new Foo { ID = 2 }, new Foo { ID = 3}, new Foo { ID = 4 } };
IEnumerable<Foo> inBoth = list1.Intersect(list2);

// now GetHashCode will be executed (not at list1.Intersect(list2))
foreach (Foo fDup in inBoth)
    Console.WriteLine(fDup.ID);

Here's a demo: http://ideone.com/ekttH3

Output:

before Intersect
after Intersect
in GetHashCode, ID=2
in GetHashCode, ID=3
in GetHashCode, ID=4
in GetHashCode, ID=1
in GetHashCode, ID=2
in Equals, ID=2
in foreach, ID=2
in GetHashCode, ID=3
in Equals, ID=3
in foreach, ID=3
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939