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.

Share on:
  • Digg
  • DZone
  • del.icio.us
  • Live
  • Google Bookmarks
  • StumbleUpon
  • Suggest to Techmeme via Twitter
  • Twitter
  1. 2 Trackback(s)

  2. Oct 11, 2009: Rick Minerich's Development Wonderland : F# Discoveries This Week 10/11/2009
  3. Jan 5, 2010: Gotchas: Common Traps for the F# n00b | #2782 - Thinking about agile (small 'a') software development, patterns and practices for building Microsoft .NET applications.

Post a Comment

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word