Custom attribute on record fields

I am trying to declare a custom attribute in a record and trying to read it. It seems to be not working. Please advise.

// Custom Attribute for record fields
[<AttributeUsage(AttributeTargets.Field)>]
type Name(x: string) =
    inherit Attribute()
    member _.value = x

// Record type
type User =
    { [<Name("id")>]
      Id: int
      [<Name("email")>]
      Email: string
      [<Name("organization_id")>]
      OrganizationId: option<string> }

// Trying to read the attribute. This is not working. I am getting <null> here.
let parse () =
    FSharpType.GetRecordFields(typeof<User>)
    |> Array.map (fun p -> p.Name, p.GetCustomAttribute(typeof<Name>, false))
    |> Array.iter (fun (t, a) -> printfn "%s : %A" t a)

This is because AttributeTargets.Field means a .NET backing field, not an F# record field. F# Record Fields are implemented as properties with hidden backing fields, and so when you use FSharpType.GetRecordFields you’re listing out PropertyInfo structures instead of FieldInfo structures.

You can experiement with this in sharplab to get a sense of how the attribute location changes. I’ve put in your sample code, and if you look at the decompiled C# you can see that the relevant structures on User look like this (I’ve copied the code for Id as an example):

        [Name("id")]
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        internal int Id@;

        [CompilationMapping(SourceConstructFlags.Field, 0)]
        public int Id
        {
            get
            {
                return Id@;
            }
        }

So the Id property fetches the value of the Id@ field. The Id@ field is the actual structure with the attribute on it. Try changing the AttributeTarget to AttributeTarget.Property and see how that changes the code and your parse function.

1 Like

@chethusk Thank you very much. That worked like a charm!!!
Also thank you for the sharp lab link.