How to check if an event is being handled in F#
What is the F# e开发者_运维技巧quivalent of the following C# code? Specifically, I need to check if an event is being handled.
protected virtual void OnClicked(ClickEventArgs e) {
if (this.Clicked != null) //how can I perform this check in F#
this.Clicked(this, e);
}
Okay, I think I figured this thing out. Taking a cue from Don Syme's blog, specifically the section "The Implementation of the IEvent Module."
Instead of the following:
let validationFailedEvent = new Event<DataValidationEventHandler, DataValidationEventArgs>()
I had to implement IEvent myself and create a variable to hold the invocation list:
let mutable listeners: Delegate = null
let validationFailedEvent = { new IEvent<DataValidationEventHandler, DataValidationEventArgs> with
member x.AddHandler(d) =
listeners <- Delegate.Combine(listeners, d)
member x.RemoveHandler(d) =
listeners <- Delegate.Remove(listeners, d)
member x.Subscribe(observer) =
let h = new Handler<_>(fun sender args -> observer.OnNext(args))
(x :?> IEvent<_,_>).AddHandler(h)
{ new System.IDisposable with
member x.Dispose() = (x :?> IEvent<_,_>).RemoveHandler(h) } }
Then, to check if there are listeners, and, if not, raise an exception:
member private x.fireValidationFailedEvent(e:DataValidationEventArgs) =
match listeners with
| null -> failwith "No listeners"
| d -> d.DynamicInvoke([| box x; box e |])
An alternative way to implement RequiresSubscriptionEvent
is to build on top of the existing Event
functionality (using composition) and just add a counter that counts the number of registered handlers and add a property HasListeners
(or even publish the number of listeners if you wanted...)
This makes the code a bit easier to use and hopefuly also safer, because if you don't check whether it has any listneres, it will still work as the usual F# code. And if you want to perform the check, you can...
type RequiresSubscriptionEvent<_>() =
let evt = new Event<_>()
let mutable counter = 0
let published =
{ new IEvent<_> with
member x.AddHandler(h) =
evt.Publish.AddHandler(h)
counter <- counter + 1;
member x.RemoveHandler(h) =
evt.Publish.RemoveHandler(h)
counter <- counter - 1;
member x.Subscribe(s) =
let h = new Handler<_>(fun _ -> s.OnNext)
x.AddHandler(h)
{ new System.IDisposable with
member y.Dispose() = x.RemoveHandler(h) } }
member x.Trigger(v) = evt.Trigger(v)
member x.Publish = published
member x.HasListeners = counter > 0
Sample usage:
type Demo() =
let evt = new RequiresSubscriptionEvent<_>()
[<CLIEvent>]
member x.OnSomething = evt.Publish
member x.FooThatFiresSomething() =
if evt.HasListeners then
evt.Trigger("foo!")
else
printfn "No handlers!"
Even though this isn't a part of standard F# libraries, it shows the great advantage of F# first class events. If there is some missing functionality, you can simply implement it yourself!
Typically, you don't need to do that check in F# (the event infrastructure checks for you):
type T() =
let ev = new Event<_>()
[<CLIEvent>]
member x.Event = ev.Publish
member x.OnClicked() =
ev.Trigger()
I followed kvb's suggestion and put this logic in a class. I copied Event from the F# sources and added a Handled property, which checks if the Delegate is null. I tried adding to, then removing handlers from the event to make sure it gets set back to null, and indeed it does.
type EventEx<'Delegate,'Args when 'Delegate : delegate<'Args,unit> and 'Delegate :> System.Delegate >() =
let mutable multicast : System.Delegate = null
static let argTypes =
let instanceBindingFlags = BindingFlags.Instance ||| BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.DeclaredOnly
let mi = typeof<'Delegate>.GetMethod("Invoke",instanceBindingFlags)
mi.GetParameters() |> (fun arr -> arr.[1..]) |> Array.map (fun p -> p.ParameterType)
member x.Handled = (multicast <> null)
member x.Trigger(sender:obj,args:'Args) =
match multicast with
| null -> ()
| d ->
if argTypes.Length = 1 then
d.DynamicInvoke([| sender; box args |]) |> ignore
else
d.DynamicInvoke(Array.append [| sender |] (Microsoft.FSharp.Reflection.FSharpValue.GetTupleFields(box args))) |> ignore
member x.Publish =
{ new IEvent<'Delegate,'Args> with
member x.AddHandler(d) =
multicast <- System.Delegate.Combine(multicast, d)
member x.RemoveHandler(d) =
multicast <- System.Delegate.Remove(multicast, d)
member e.Subscribe(observer) =
let h = new Handler<_>(fun sender args -> observer.OnNext(args))
(e :?> IEvent<_,_>).AddHandler(h)
{ new System.IDisposable with
member x.Dispose() = (e :?> IEvent<_,_>).RemoveHandler(h) } }
This article here http://geekswithblogs.net/Erik/archive/2008/05/22/122302.aspx says you do not need to check for null events in F#, though I don't know what his reference is.
This article http://blogs.msdn.com/dsyme/articles/FSharpCompositionalEvents.aspx by Don Symes goes into F# events in quite a bit of detail. It looks like events are not owned by the class in F#
From the above,
it is that events are now first-class values in the F# langauge. Indeed, events are not a separate notion at all in the language design, rather, events are just values of type Microsoft.FSharp.Idioms.IEvent<_>, and .NET events are effectively just properties of this type.
And
One of the restrictions of C# is that events can only exist as members within classes. With the F# model, new event values can be created just as values as part of any expression.
精彩评论