For years on Windows, installing a new program could quietly break programs you already had. The cause was the way shared code was packaged. Many applications relied on the same dynamic-link libraries (DLLs), and an installer would often drop its own copy of a shared DLL into a common system folder, overwriting whatever was there. If the new copy was a different version, every other app that expected the old behavior could suddenly fail. The condition became infamous as “DLL hell.”
Microsoft’s own documentation states the root problem directly: if an application “depends on a specific version of a shared DLL, and another application is installed with a newer or older version of that DLL, then that can cause compatibility problems and instability; it can cause your app to start to fail” (https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-redirection). Because there was effectively one shared location and no enforced versioning, the last installer to write a DLL won, and the losers crashed.
COM made it worse. As Microsoft notes, “two incompatible versions of a COM server can be installed and registered (even in different file system locations), but there’s only one place to register the COM server. So only the latest registered COM server will be activated” (https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-redirection). The registry kept a single pointer, so a new install could redirect every program to a version it was never tested against.
Microsoft’s responses attacked the lack of isolation. One was DLL redirection, letting an app load a private copy of a DLL from its own folder first. Another was side-by-side assemblies, which allow multiple versions to coexist. .NET went further: as the assembly versioning documentation explains, “two assemblies that differ by version number are considered by the runtime to be completely different assemblies,” and “the default version policy for the runtime is that applications run only with the versions they were built and tested with” (https://learn.microsoft.com/en-us/dotnet/standard/assembly/versioning). By making version part of an assembly’s identity, .NET let conflicting versions live together instead of overwriting each other, draining the swamp that DLL hell had been.