Oh… this is so fundamental and, at the same time, so basic question each and every .NET developer should perfectly understand.
I'll start from the purely practical rule of thumb: in most cases, only "Any CPU" target should be used. Then, as you correctly put it, JIT will take care of the rest. (For those not properly understanding JIT, I'll just mention: .NET code is compiled into CIL code, and CIL code is compiled into native CPU instructions when the application is already running, on per-method basis, as soon as some method is required to be called:
Common Intermediate Language - Wikipedia, the free encyclopedia[
^],
Just-in-time compilation - Wikipedia, the free encyclopedia[
^].)
The other, concrete platform targets should be used only in some special cases. I guess, the remaining problem for me is to describe those special cases. The workflow and the decisions-making is pretty easy, but the explanation cannot be too easy.
First of all, one needs to understand that supported 64-bit
instruction-set architectures are not compatible with each other, but the 64-bit architectures IE-64 (Itanium) and x86-64 (AMD64) are compatible with x86 (32-bit). On 64-bit versions of Windows, x86 processes are supported via the WoW64 subsystem. Please see:
Itanium - Wikipedia, the free encyclopedia[
^],
x86-64 - Wikipedia, the free encyclopedia[
^],
x86 - Wikipedia, the free encyclopedia[
^],
WoW64 - Wikipedia, the free encyclopedia[
^].
Another fact is: all the assemblies loaded by some application should use compatible instruction-set architectures. It means that some assemblies can be compiled to some concrete architectures, and some — to "Any CPU", but all the assembles compiled to any concrete architecture should be compiled to exactly the same architecture. For example, you can combine x86 with "Any CPU", but never x86 with x86-64 or x86-64 with Itanium, and so on. Naturally, the OS should match it, or, alternatively, the architecture should be x86. Interestingly, it's possible to mix wrong set of target architectures on build, but the application will crash; and this is a real "crash", not a recoverable exception.
Keeping this in mind, we can create an application using just one chosen target architecture which should match the OS (not just physical CPU used on the system). Only now, we are coming to the key issue: why doing so, in practice?
I can see only one reason to do so:
compatibility with some native-code module. A very typical situation is: you use some native (unmanaged) module. Usually, this is some 3rd-party product, but it could be your own module which you simply cannot rewrite to some .NET language, by one or another reason. Unmanaged modules don't have "Any CPU" target, because they don't use JIT. You can link such modules using P/Invoke or C++/CLI:
Platform Invocation Services - Wikipedia, the free encyclopedia[
^],
C++/CLI - Wikipedia, the free encyclopedia[
^],
Standard ECMA-372[
^],
Calling Native Functions from Managed Code[
^].
One practical way to develop such application is: in .NET assemblies, use "Any CPU" whenever possible. You can control the ultimate choice of the target architecture by only one assembly, the application one. It's architecture should match the architecture of all the unmanaged (native) modules and target OS. Remember that x86 can be used for compatibility, bit Itanium systems are much less compatible to x86 than x86-64, the most widely used compatible architecture.
See also my past answer where I explain why it's important to avoid P/Invoke whenever possible:
How to play beeps in C#[
^].
Nevertheless, there are cases when you don't have a choice. And those are the cases when using a concrete target architecture can save your project.
—SA