I just spent some hours performance optimizing an application I wrote a while back. The architecture was actually quite nice and I had implemented my own Lazy-Loading using DynamicProxy. Things worked great and performance was never an issue.
Fast forward 2 years: I left the company after the project was finished and development carried on. Additions to the code where made and after some time performance got a serious problem.
After hours of profiling I finally found the issue: Apparently List<T>.AddRange invokes GetHashCode on all items you add to the list.
Why is that a problem? Well: My Proxy objects where designed to return some key data that was eagerly fetched (stuff like Id, and some filter columns) and once the filter logic had narrowed down the list of thousands of items to the 2 or 3 that were going to be displayed, the Proxy transparently fetched the real data from the database and cached it. So every object in itself was doing a select once a method besides the prefetched data was requested.
I hadn’t thought of GetHashCode and any call to GetHashCode was not handled directly in the Proxy but caused a lazy load. And since .Add does not call GetHashCode but AddRange does, the bug never came up during development.
Imagine my face when I saw this profiler graph:
Needless to say that fetching 64 items from the database instead of 1 really brought the app down to a crawl.