Search code examples
c#f#nameof

nameof limits in F#


in C# WPF we can use nameof as in

public class MyControl : Control
{
    private static readonly DependencyProperty _myProperty =
        DependencyProperty.Register(nameof(MyProperty), typeof(int), typeof(MyControl));

    public int MyProperty
    {
        get => (int)GetValue(_myProperty);
        set => SetValue(_myProperty, value);
    }
}

but in F# this doesn't work

type MyControl() as this =
    inherit Control()

    static let _myProperty = 
        DependencyProperty.Register(nameof MyProperty, typeof<int>, typeof<MyControl>) //The value or constructor 'Bars' is not defined.

    member this.MyProperty
        with get() = this.GetValue(_myProperty) :?> int
        and set(value: int) = this.SetValue(_myProperty, value)

The easiest way is to use a string "MyProperty" but this has the notorious error prone effect when renaming the property if we forget to change the string.

Is there a way to preserve the nameof safety that C# gives in F#?

A way I found is

open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns

let getPropertyName (expr: Expr<'T -> 'U>) =
    match expr with
    | Lambda(_, PropertyGet(_, propInfo, _)) -> propInfo.Name
    | _ -> failwith "Invalid expression: Expected a property getter."

type MyControl() as this =
    inherit Control()

    static let _myProperty = 
        DependencyProperty.Register(getPropertyName <@ fun (c: MyControl) -> c.MyProperty @>, typeof<int>, typeof<MyControl>)

    member this.MyProperty
        with get() = this.GetValue(_myProperty) :?> int
        and set(value: int) = this.SetValue(_myProperty, value)

but it is clearly cumbersome.

It seems a limit of the nameof feature in F# against C#. Is there a better way?


Solution

  • This is a known limitation when using nameof on an instance member. The recommended workaround is to use Unchecked.defaultof:

        static let _myProperty =
            let name = nameof Unchecked.defaultof<MyControl>.MyProperty
            DependencyProperty.Register(name, typeof<int>, typeof<MyControl>)