There are two types of assembly identity that the loader deals with: bind-time and after bind-time. The identity is used to determine whether we will consider a certain assembly to be the same thing as an assembly reference or another assembly.

Assembly Binding
At assembly-bind time, the components of the assembly display name determine identity. This will be used to match an assembly reference (the assembly that the user wants to load) to a manifest file.

Not all of the components matter in all cases. If the assembly is not strongly-named, then the version is ignored for binding. But, if it is strongly-named, the entire version in the assembly reference needs to match the found assembly.

There are other details about what is and isn't necessary to specify for binding, but please always give the entire display name EVERY time, anyway. Otherwise, you are asking for trouble!!

Comparing Already-Loaded Assemblies
Unless this comparison is for binding, the path to the manifest file of this assembly is used for determining whether this is the same assembly as another one.

For example, path matters when determining whether a type is castable to another type. Even if the assemblies containing the types are identical, if they're loaded from different paths, they're considered different assemblies and therefore their types are different. This is one reason why using contexts other than the Load context is risky. You can get into situations where the same assembly is loaded multiple times in the same appdomain (once in the Load context, once in the LoadFrom context, and even several times in neither context), and their corresponding types won't be castable.

Some assemblies don't have paths associated with them, such as those loaded by Load(byte[]) or created by Reflection Emit. Those are always just considered different assemblies, even if all of their bytes are identical.