Why does decompiling an F# assembly produce C# code?
It doesn't. It produces IL code which then can be interpreted as C#, F# or VB.NET. dotPeek doesn't allow you to use any other language for previewing but for example dnSpy, ILSpy allow you to pick your favourite one. Just wondering if there's any tools that allows F#. Not sure/haven't seen one.
Despite this, are there reliable methods for distinguishing C# binaries from F# ones via static analysis? Dynamic analysis?
There are some hints that can guide you. Check the references. If something is referencing the
FSharp.Core then probably was written in F#. On the other hand if it doesn't probably it was not :)
F#'s dlls usually have a lot of attributes put on classes like
[CompilationMappingAttribute] being used. Check them.
There's one more thing. F# compiler is capable of using a tail-recursion by generating tailcall opcode which is not the case with C# (at least for now). That could be an indicator. But it could be also manually crafted to use it after being written completely in C#.