You need to cast the final value:
let FromBytes<'a> : (byte[] * int -> 'a) =
match typeof<'a> with
| x when x = typeof<Int16> -> downcast (BitConverter.ToInt16 |> box)
| x when x = typeof<UInt16> -> downcast (BitConverter.ToUInt16 |> box)
| _ -> failwith "Unknown type"
This will check the type at run-time and select the proper case, there is also a trick to do it at compile time using static constraints, but if you are learning it might be really confusing:
open System
type T = T with
static member ($) (T, _: int16) = fun v s -> BitConverter.ToInt16 (v,s)
static member ($) (T, _:uint16) = fun v s -> BitConverter.ToUInt16(v,s)
static member ($) (T, _: int ) = fun v s -> BitConverter.ToInt32 (v,s)
static member ($) (T, _:uint32) = fun v s -> BitConverter.ToUInt32(v,s)
let inline fromBytes (value:byte[], startIndex:int) =
(T $ Unchecked.defaultof< ^R>) value startIndex : ^R
// usage
let (x:int ) = fromBytes([|255uy;0uy;0uy;255uy|], 0)
let (y:uint16) = fromBytes([|255uy;0uy;0uy;255uy|], 0)
The F# compiler inlines the required function at the call site, you can't call the generic function from C#.