The new release, dubbed ‘Trumpet-2’, completes the work started by the previous release (Trumpet) by adding full integration with .Net Events. And ‘full’ means just what it says:
You can write Essence# code that adds and removes event handlers to any .Net event.
You can write Essence# code that raises any .Net event.
Also, blocks have been extended so that they can act just like .Net multicast delegates, and they can also be added to the invocation lists of any .Net delegate. That means that you can even publish blocks to the .Net world as either delegates or even as events, because .Net code can add “handlers” to an Essence# block as though it were a .Net event (although .Net 4.0’s “dynamic” type must be used.)
These new features/capabilities remove the last obstacle to building a GUI in Essence#.
The Trumpet-2 release can be downloaded from the Essence# site on CodePlex (which gets you the installer.) Alternatively, the code for the run time system (written in C#) can be obtained from theEssence# CodePlex site using Git, and the code for the Essence# Standard Library can be obtained from GitHub. And the example scripts can also be downloaded from the Scripts repository on GitHub.
Essence# Blocks As .Net Multicast Delegates
Essence# blocks have always been implemented using .Net delegates, although a block instance is not itself a .Net delegate. Instead, an Essence# block encapsulates a .Net delegate. Briefly, a .Net delegate is an object-oriented function pointer, in the same sense that a .Net (or Essence#) object is an object-oriented data pointer: You can’t access or mutate the value of the pointer as an address value (e.g., a number), but that’s what it is “under the hood.”
What’s different about .Net delegates with respect to their analogs on other platforms is that a delegate can point to more than one function at a time–provided they all have the same parameter signature. When a delegate is invoked, all the functions in its invocation list will be executed. Whether that’s a good feature to be built into a language (or in this case, into a run time system) is an interesting topic, but that’s water under the bridge: It’s the reality of the .Net environment, and full interoperability with .Net requires that languages implemented on the .Net CLR support it.
Although the current documentation does not discuss this much (or at all,) it is usually possible to use a “raw” .Net delegate in Essence# code as though it were an Essence# block–and vice versa. Until now, one major exception to that has been adding and removing functions to the invocation list of a .Net delegate.
As of this release, you can set or get the delegate encapsulated by an Essence# block by sending it the message #function (to get the delegate) or #function: aDelegate (to set the delegate.) You can also now send the message #+= aBlockOrDelegate in order to add the function (or functions) of aBlockOrDelegate to the invocation list of the delegate encapsulated by the block that is the receiver of the message. And you can now send the message #-= aBlockOrDelegate in order to remove the function (or functions) of aBlockOrDelegate from the invocation list of the delegate encapsulated by the block that is the receiver of the message. Note, however, that in either case the receiver must be an Essence# block.
Additionally, the message #+ aBlockOrDelegate can be sent to a delegate: It answers a new delegate that combines the invocation lists of the two message operands (the receiver and the message argument.) And the message #- aBlockOrDelegate can be sent to a delegate: It removes the invocation list of the argument from the receiver.
Using .Net Events
To subscribe to a .Net event, send the message #onEvent: eventName do: aBlockOrDelegate to the object that defines the event (it must be a native CLR object, not an Essence# object.)
To unsubscribe from a .Net event, send the message #onEvent: eventName doNotDo: aBlockOrDelegate to the object that defines the event (it must be a native CLR object, not an Essence# object.)
To raise a .Net event, it is first necessary to add a user primitive to the Essence# class that represents the CLR type of the object that defines the event, as in the following example:
protocol: #events method: [## invokeTickEvent: eventSource args: eventArgs <invokeEvent: #tick> ]
Note that the number and order of the arguments of the Essence# method selector must match the order and number of the arguments of the .Net event handler type defined for that event by the .Net type that defines the event. The symbol (or String, or identifier–any will work) that follows the “invokeEvent:” keyword must match the name of an event defined on the .Net type whose instances will receive the message defined by the “invokeEvent:” primitive.
Once there is such a primitive, the event with which the primitive is associated can be raised by sending the message defined by the primitive (which in the case of the above example, would be #invokeTickEvent: theEventSource args: anEventArgs) to an object whose type defines the event (and whose Essence# class defines that primitive.) Note that one would usually send such a message to the same object that is passed in as the first message argument (“theEventSource” in the example.) It would usually be a good idea to define a “helper” method in the same class that simplifies the API for raising the event, such as in the following example:
protocol: #events method: [## announceTick self invokeTickEvent: self args: self newTickEventArgs ]
With such a helper method defined, the event can be raised simply by sending the message #announceTick to the event source. Obviously, there would also need to be an implementer of the #newTickEventArgs method, written either in Essence#, or in whatever language implements the .Net type that defines the class/struct that defines the event.
Integration of .Net Events With Essence# Announcments
In order to provide a bridge bewteen .Net events and Essence# Announcements, the following methods have been added to CLR.System.Object:
protocol: #'events-subscribing' method: [## when: anAnnouncementClass do: anAction ^self onEvent: anAnnouncementClass eventName do: anAction ]; protocol: #'events-subscribing' method: [## when: anAnnouncementClass doNotDo: anAction ^self onEvent: anAnnouncementClass eventName doNotDo: anAction ]
By default, the #eventName message answers the name of the Announcement class that receives it (or the name of the class of the receiver, if the receiver is an instance of an Announcement class.) However, subclasses of Announcement may override that implementation as needed.
Here’s some example code that you can actually run (using the ES command-line tool) to see .Net events being raised and handled by Essence# code:
| metronome handler | metronome := Examples.Metronome new. handler := [:metronome :eventArgs | CLR.System.Console writeLine: 'Tick!']. metronome onEvent: #tick do: handler. metronome announceTick. metronome onEvent: #tick doNotDo: handler. metronome announceTick
The C# class EssenceSharp.Examples.Metronome has been made a permanent part of the Essence# distribution in order to be used as an example and in unit tests. The Essence# class that represents it can be found at Standard.lib \Examples\Metronome. And a new script named Events.es that contains the above code has been added to the list of example scripts.