Gotchas: Adding Attributes to Properties on Interfaces in F#

Tuesday, September 15, 2009 – 9:49 AM

I ran into a couple of issues when writing an F# class to be consumed by C#. Specifically when it came to adding attributes to a property declared on an interface. You would expect the following code to decorate the SofteningLength property with the Dependency and DefaultValue attributes:

   1: #light
   2:  
   3: namespace NBody.DomainModel.Integrators
   4:  
   5: open System
   6: open System.ComponentModel
   7: open NBody.DomainModel
   8: open Microsoft.Practices.Unity
   9:  
  10: [<Description("Forward Euler (F#)")>]
  11: type public ForwardEulerFSharpIntegrator() = class 
  12:   let mutable softeningLength = 0.0
  13:   let mutable isInitialized = false 
  14:  
  15:   // IIntegrate implementation
  16:     
  17:   interface IIntegrate with 
  18:       
  19:     [<property: Dependency("SofteningLength")>]
  20:     [<property: DefaultValue(0.01)>]
  21:     member public this.SofteningLength 
  22:       with get() = softeningLength 
  23:       and set(value) = softeningLength <- value
  24:     
  25:     member public this.Initialize(bodies) = 
  26:       isInitialized <- true
  27:  
  28:     member public this.Integrate(dT, (bodies:Body[])) = 
  29:       if not isInitialized then raise (InvalidOperationException("inner"))    
  30:         
  31:   end
  32: end

If it all worked I wouldn’t be writing this post. Turns out this is a bug in the F# May 2009 CTP (and probably Visual Studio 2010 Beta 1 & 2).

No attributes means that my code breaks are runtime. The Unity dependency injection container my application uses cannot create the object correctly as it doesn’t see the SofteningLength property as a dependency and other code I wrote to read the default value also fails to find the attribute. Looking at the IL or C# disassembly in Reflector shows that the attributes were not generated.

Workaround

Turns out that a subtle slight of hand can be used to “fix” the issue. Interfaces in F# are explicitly implemented. In C# the F# code I wrote above would look like this:

   1: [Dependency("SofteningLength")]
   2: [DefaultValue(0.01d)]
   3: double IIntegrate.SofteningLength { get; set; }

This isn’t actually what I want. In most cases I’d be happy to have my interface implicitly implemented (the default in C#).

However, by implementing the same methods and properties on my ForwardEulerFSharpIntegrator type I can trick the calling code into finding the correct attributes:

   1: #light
   2:  
   3: namespace NBody.DomainModel.Integrators
   4:  
   5: open System
   6: open System.ComponentModel
   7: open NBody.DomainModel
   8: open Microsoft.Practices.Unity
   9:  
  10: [<Description("Forward Euler (F#)")>]
  11: type public ForwardEulerFSharpIntegrator() = class 
  12:   let mutable softeningLength = 0.0
  13:   let mutable isInitialized = false 
  14:  
  15:   // IIntegrate implementation
  16:     
  17:   interface IIntegrate with 
  18:       
  19:     member public this.SofteningLength 
  20:       with get() = softeningLength 
  21:       and set(value) = softeningLength <- value
  22:     
  23:     member public this.Initialize(bodies) = 
  24:       isInitialized <- true
  25:  
  26:     member public this.Integrate(dT, (bodies:Body[])) = 
  27:       if not isInitialized then raise (InvalidOperationException("inner"))    
  28:         
  29:   end
  30:       
  31:   // Implicit implementation of IIntegrate, also allows attributes to be set correctly
  32:     
  33:   [<property: Dependency("SofteningLength")>]
  34:   [<property: DefaultValue(0.01)>]
  35:   member public this.SofteningLength 
  36:     with get() = (this :> IIntegrate).SofteningLength
  37:     and set(value) = (this :> IIntegrate).SofteningLength <- value
  38:     
  39:   member public this.Initialize(bodies) =
  40:     (this :> IIntegrate).Initialize(bodies)
  41:       
  42:   member public this.Integrate(dT, bodies) =
  43:     (this :> IIntegrate).Integrate(dT, bodies)
  44:       
  45: end

In some cases I will call the SofteningLength property setter on the explicit interface but rely on the implicit implementation to get the attributes. Note how the implicit implementation casts (using the :> operator) to the interface and then calls the explicit implementation.

This seemed like it was worth writing up as I couldn’t find anyone else who’d blogged about it. I’ve also told the F# team so they’re aware of the issue.