While playing with AutoFixture to verify if F# Records can be marshalled as nicely as Structs, to my surprise I noticed MarshalAsAttribute cannot be accessed by standard GetCustomAttributes method. Turns out that it’s one of pseudo custom attributes that are treated differently by compiler.

Why would I need to access MarshalAsAttribute?

AutoFixture by default generates test data for string as memberName+GUID, which is nice for most cases, but if a filed is marked as having 10chars in string, why provide with incorrect data?

type SampleRecord = {
  [<field: MarshalAs(UnmanagedType.I1)>]boolVar:BOOL
  byteVar: BYTE
  dwordVar: DWORD
  lrealVar: LREAL
  [<field: MarshalAs(UnmanagedType.ByValTStr, SizeConst=81)>]
  string80Var: string
  [<field: MarshalAs(UnmanagedType.ByValTStr, SizeConst=11)>]
  string10Var: string

Possible solution would be usage of additional attribute like [<field: StringLength(10)>], but I’m not a big fan of providing same information, over and over again, if I already did it once, plus sometimes I’m stubborn, well, quite often if I’m positive something can be done the way I want it.

How to access MarshalAsAttribute?

If you look into .Net Reference Source you’ll find internal static GetCustomAttribute(RuntimeFieldInfo field) method, which returns the very same MarshalAsAttribute as we annotated our field with. Few pretty self-explanatory lines of code and we’re home.

module Extensions

open System.Reflection
open System.Runtime.InteropServices

let maa = typedefof<MarshalAsAttribute>
let GetCustomAttribute = 
  maa.GetMethods(BindingFlags.Static ||| BindingFlags.NonPublic) 
  |> List.ofArray 
  |> List.tryFind (fun mi -> 
    let parameters = mi.GetParameters()
    mi.Name = "GetCustomAttribute" 
    && parameters.Length = 1 
    && parameters.[0].ParameterType.Name = "RuntimeFieldInfo"

type FieldInfo with
  member self.GetMarshalAsAttribute () =
    match GetCustomAttribute with
      | Some(gcaMi) ->
        let attr = gcaMi.Invoke(null, [| self |])
        Some(attr :?> MarshalAsAttribute)
      | None -> None