(Warning: here I am making some "educated guesswork", notably by analogy with what happens in other operating systems.)
The behaviour you describe is normal: a .NET executable is actually a kind of script. From the point of view of the operating system kernel, there is no ".NET". The kernel knows of executable files. For the kernel, a file can be accessed for reading (an application opens the file and then read the bytes with calls like ReadFile()) or for executing (see CreateProcess()). When executing some binary files, the file contents are mapped into RAM through the MMU; at the MMU level, pages have both read and execute rights enabled. However, if the file to execute is a script, then the binary file which actually gets mapped into RAM is not the script file itself, but the interpreter. The interpreter then reads the script contents as data.
Thus, when an executable file is a script, then the actually required access rights are "execute" on the interpreter, and "read" on the script file. The OS will also require the "execute" right on the script file before accepting to locate and launch the interpreter.
A .NET executable file is, from the point of view of the Windows kernel, a kind of script; its interpreter is the CLR virtual machine. For the kernel, the CLR is nothing special. The CLR will read the .exe contents, find the bytecode and interpret it (that "interpretation" will include some JIT compilation but that's another story).
In any case, even if a user has only the "execute" right on a binary file but not the "read" right, then chances are that the user can attach to the resulting process and then read the file that way -- because, at the MMU level, the pages containing the code will be both executable and readable. It seems most unwise to rely on these access rights to prevent reverse-engineering.