Note: Most of what I'm saying is based on Java, but as I understand it, CLR operates in pretty much the same way.
Basically, the way it works is that the compiler converts your source code into a format known as bytecode, which can then be executed by a VM. Generally, compilers don't bother optimizing the code they generate, because it will be optimized at runtime by the VM anyway. So if the code was compiled by a standard compiler and not obfuscated, the translation to bytecode is very direct and predictable, meaning that you can decompile it into reasonable looking source.
However, you will still lose anything that is basically syntactical sugar. The compiler will only include stuff that is necessary for execution. Luckily, reflection support (and debugging if enabled) means that a lot of source level information will be preserved in the bytecode, probably through optional metadata. But stuff like whitespace and comments are not accessible even with reflection, so there is no way to recover them.
The analogy with minified JS is not exact but it is still useful. In the case of Javascript, the source files are the input to the VM, so there is no visible intermediate bytecode stage. Minification is the result of an optimizer going through and reformatting the source code, but it is still source code. On the other hand, in both cases the lost information is the result of a tool not preserving it due to it being unnecessary for execution.
If the files were obfuscated, then all that goes out the window. Obfuscators deliberately mess up the patterns introduced by the compiler and will remove all the optional metadata that they can. You can often still decompile obfuscated code, but it will be a mess and doesn't bear the helpful information of the original source such as formatting and variable names.