Thanks to .NET, DLL Hell is officially ended. But I at least still find myself in DLL Heck from time to time (thanks to Scott Adams for the term).
We still seem to have too many issues where something works on one machine but not on another (usually it works on the developer's machine but not when we install it on a director's laptop to show him that we haven't spent the last month just browsing the Internet).
Our app has a simple footprint: we bung everything, including third-party controls, into a bin folder. How can anything go wrong? The loader looks in the app folder for stuff first, doesn't it? WRONG!
We get away this simple-minded view most of the time because (and I know you'll tell me off for this) we aren't yet strongly naming our assemblies. In this case, the loader only looks in the app folder (and below).
But for strongly named assemblies, the loader looks in the GAC first. This makes sense. The loader is always looking for a specific version of an assembly; you control which assembly versions are loaded for your app via the version policy, not by shadowing shared DLLs with your own copies in the app bin folder.
Here are some links that explain all this unambiguously.
Assembly Versioning (from the MSDN Library)
Resolving Names to Locations (from .NET Common Language Runtime Components by Don Box & Chris Sells)
Useful diagram from the second link:
Bonus information from that book extract: if you put <developmentMode developerInstallation="true" /> in the configuration/runtime node of the machine.config file, the loader first looks in the path specified by the DEVPATH environment variable.