Search code examples
eventsf#.net-8.0

F# event with exposed add/remove functions and consumable from C#


With reference to https://stackoverflow.com/a/4964791/2323934, I am trying to use that code in .NET 8

let customEvent add remove =
  { new IDelegateEvent<_> with
        member this.AddHandler del = add del
        member this.RemoveHandler del = remove del }
...
    let clockEvent = customEvent (fun _ -> ()) (fun _ -> ())

    [<CLIEvent>]
    member __.ClockEvent = clockEvent
...

It seems to work as there are no intellisense errors. However the project raises a build error

Error FS1091 The event 'ClockEvent' has a non-standard type. If this event is declared in another CLI language, you may need to access this event using the explicit add_ClockEvent and remove_ClockEvent methods for the event. If this event is declared in F#, make the type of the event an instantiation of either 'IDelegateEvent<>' or 'IEvent<,_>'.

Note that the error disappears if I remove the [<CLIEvent>] line, but then the event cannot be consumed in C#

enter image description here How to fix this error?


Solution

  • Your event ends up generic. If you look closely, you'll see that while it's known that ClockEvent should have type IDelegatedEvent<'a> (because customEvent returns a IDelegatedEvent), it is not known what 'a is.

    This means that member ClockEvent is generic (has a type parameter 'a), and generic events aren't allowed.

    To fix, just add a type annotation somewhere, so that the compiler can infer the type. Could be here:

    member __.ClockEvent : IDelegateEvent<System.EventHandler> = clockEvent
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    Or could be here:

    let clockEvent = customEvent (fun (_ : System.EventHandler) -> ()) (fun _ -> ())
                                           ^^^^^^^^^^^^^^^^^^^
    

    Or even on customEvent's signature:

    let customEvent (add : System.EventHandler -> _) remove =
                           ^^^^^^^^^^^^^^^^^^^
    

    Doesn't matter where exactly, the compiler just needs to know the type you want somehow. Right now you're not telling it.

    (obviously replace System.EventHandler with the actual delegate you want there; I used EventHandler just for example, because it's the most basic one)