Consuming an event creates a closure, requiring a as self reference. This doesn't seem necessary.
type A() =
[<CLIEvent>]
member val Event =
(new DelegateEvent<EventHandler>()).Publish
type B() =
inherit A()
do base.Event.Add ignore // Error: 'base' values may only be used to make direct calls...
Even let x = base.Event is not allowed.
NOTE: the workaround is to use the implicit add_Event explicitly:
open System
type A() =
[<CLIEvent>]
member val Event =
(new DelegateEvent<EventHandler>()).Publish
type B() =
inherit A()
do base.add_Event (fun _ _ -> ())
@charlesroddie
You declared a member in the base class, so the correct way to access it is through the 'as self' definition. Calling base in this case is wrong, since it's reserved for overriding members only.
Now, that said, having to not have to declare the self reference to access public members exposed by the parent type would be nice. I read somewhere that the IL code generated by the self ref at the class level was kinda bad, but in case you need it for constructor code, I don't see another, clear, way.
@heronbpv the code below declares two members in the base class, and calling base is right in those cases, and works:
type X() =
member val Z = 5
member val FSharpEvent = Event<unit>().Publish
type Y() =
inherit X()
let y = base.Z
do base.FSharpEvent.Add ignore
Interesting, this directly contradicts the error message, which states that "'base' values may only be used to make direct calls to the base implementation of overridden members". Never heard of using base to access inherited members outside of trying to modify an overridden methods' behavior, and to me being able to do so smells of bug... with the caveat that I don't know if this is also possible in C# or VB.
Alas, if it helps, removing the CLIEvent attribute seems to sidestep your problem, as tested on a netcoreapp3.1 script:
open System
type A() =
//[<CLIEvent>]
member val Event2 =
(DelegateEvent<EventHandler>()).Publish
member val Event3 =
Event<unit>().Publish
type B() =
inherit A()
do base.Event2.AddHandler (EventHandler(fun sender args -> ()))
do base.Event3.Add ignore
removing the CLIEvent attribute
The typical use cases are for consumption of CSharp Events, e.g. for UI code where you might want to inherit from a View.
contradicts the error message
Yes there seems to be an inconsistency here.
Never heard of using base to access inherited members outside of trying to modify an overridden methods' behavior, and to me being able to do so smells of bug... with the caveat that I don't know if this is also possible in C# or VB
Interestingly there isn't anything in the 4.1 language spec on this, but online docs say:
You can access the methods and properties of the base class from the derived class by using the language keyword base as an identifier, followed by a period (.) and the name of the member.
The current behavior seems right so we should just adjust the error message. The status quo is that base can be used for simple access to members of the base class (only requiring that the base class has been constructed, which happens early so is guaranteed. However closures containing base aren't allowed (not sure the details about why this is a problem). In that case you have to rely on as self, which goes through various checks that the object in the derived class has been defined, which reduce efficiency and can fail at runtime.
Example showing difference between base and as self:
type X() =
member val Z = 5
type Y1() =
inherit X()
let y = base.Z
type Y2() as self =
inherit X()
let y = self.Z
SharpLab output:
public class Y1 : X
{
public Y1()
{
int z = base.Z;
}
}
public class Y2 : X
{
internal int init@8;
public Y2()
{
FSharpRef<Y2> fSharpRef = new FSharpRef<Y2>(null);
base..ctor();
fSharpRef.contents = this;
int z@ = LanguagePrimitives.IntrinsicFunctions.CheckThis(fSharpRef.contents).Z@;
init@8 = 1;
}
}
Example showing invalid closure:
type Y1() =
inherit X()
let y() = base.Z // FS0408 The 'base' keyword is used in an invalid way. Base calls cannot be used in closures...
I suspect that the reason the original code in this issue doesn't work is that a closure is created and that the error message for the current implementation of events should be "base calls cannot be used in closures".
Interesting. The inheritance article in the docs also mentions this: "The keyword base is available in derived classes and refers to the base class instance. It is used like the self-identifier". So you're on point when you say that using base as the way to access members from the parent class is acceptable in F#. Never saw that up until now, TIL!
So now, what we do have is two erros messages to correct, right?
Just out of curiosity, but shouldn't the closure issue only matter if the method isn't public? Or does it give problems with virtual dispatch somehow?
The workaround is to use add_Event
open System
type A() =
[<CLIEvent>]
member val Event =
(new DelegateEvent<EventHandler>()).Publish
type B() =
inherit A()
do base.add_Event (fun _ _ -> ())
There is material missing from the F# specification on this. If Event is a CLIEvent, then expr.Event gets elaborated to
let eventTarget = expr
CreateEvent(
(fun eventDelegate -> eventTarget.add_Event(eventDelegate)),
(fun eventDelegate -> eventTarget.remove_Event(eventDelegate)),
(fun callback-> new EventHandler(fun delegateArg0 delegateArg1 -> callback delegateArg0 delegateArg1)
)
This is part of the "facade" of pretending that CLI events are first class values. The directive CLIEvent says "don't generate a real property for this event", but that means we patch up the first-class event on the usage side.
Here the problem is that if eventTarget is base then the resulting elaboration triggers the base error (and even if base is pushed inside the closure). This elaboration causes problems elsewhere too, there are a couple of outstanding issues with it.
Most helpful comment
The workaround is to use
add_EventThere is material missing from the F# specification on this. If
Eventis a CLIEvent, thenexpr.Eventgets elaborated toThis is part of the "facade" of pretending that CLI events are first class values. The directive
CLIEventsays "don't generate a real property for this event", but that means we patch up the first-class event on the usage side.Here the problem is that if
eventTargetisbasethen the resulting elaboration triggers thebaseerror (and even ifbaseis pushed inside the closure). This elaboration causes problems elsewhere too, there are a couple of outstanding issues with it.