C# style events


#1

Similarly to C#, it would be nice to have language support for events. For any of you, who don’t know how events in C# work, here are the basics: https://www.tutorialspoint.com/csharp/csharp_events.htm.

  1. Events are very convenient and remove a lot of boiler plate code.
  2. Class interface becomes simpler. No addListener, removeListener, setListener methods.
  3. You don’t have to manually store the handlers references.
  4. It’s easy to invoke multiple handlers.
  5. Implement only the callbacks you need and not all of them like in interfaces.

Proposed Kotlin syntax:

class Foo {

    companion object Bar {
        public event yoloed(x: Int, y: Int) // static event
    }

    public event swaged(baz: String) // declaration

    fun qux() {
        swaged += onSwag // subscription
        swaged("coorge") // invocation   
    }

    // event handler
    fun onSwag(x: Int, y: Int) { ... }
}

#2

I’m don’t think language support for this is necessary. Take a look at this extremely basic implementation: https://gist.github.com/Hikaru755/b430927d4233b0876618090f00c3110a

Using that you can write your example as follows:

class Foo {

    public swaged: Event<String>() // declaration

    fun qux() {
        swaged += { onSwag(it) } // subscription
        swaged("coorge") // invocation   
    }

    // event handler
    fun onSwag(x: String) { ... }
}

Except for the minimal overhead with the lambda braces this is not less concise, and is done using only existing kotlin features. And I’m not even sure the lambda notation there is even necessary, but I’m not quite up to speed about the current state of function references in kotlin.


Support lazy argument in functions
#3

I think that C#-style events are an unnecessary and somewhat strange special case. If I remember correctly you can set an event to null to remove all event handlers, but then you can add event handlers to the just nulled event. That doesn’t make make much sense.


#4

If I remember correctly you can set an event to null to remove all event handlers, but then you can add event handlers to the just nulled event. That doesn’t make make much sense.

What is weird about it? How does this relate to the advantages I listed? This is just semantics, they don’t have to be implemented exactly like in C#.


#5

Thanks, I’ve seen it. This indicates that events are easy to implement in Kotlin and fit the language nicely. Official support would take it to the next level and make it a standard. A standard that already proved itself in C#.


#6

To be honest I don’t think standardizing it in the language is not the way to go, for several reasons, as that would dictate a certain way of doing things on a level that third-party libraries can’t compete with, even if they actually do the job better. Think about it, events, though trivially to implement, have quite some things to consider. For example, should listeners be called on the thread where the event is triggered, or where they subscribed? What about a completely different thread? You might care about the order in which listeners are called, or you could be more concerned with speed and call them in parallel instead. You could want to debounce events. All these special cases are things that dedicated libraries are suited to specialize in, without the need for one language-level implementation others have to build around. It’s the same deal as with concurrency, really, the same reason kotlin chooses to provide basically no constructs for concurrency and instead hand that off to external libraries.


#7

I’m glad that you asked those important questions! Let’s consider them one by one.

should listeners be called on the thread where the event is triggered, or where they subscribed

Definitely on a trigger thread. Just like you’d invoke a function reference.

You might care about the order in which listeners are called, or you could be more concerned with speed and call them in parallel instead

I believe that all threading should be done on a receivers side. But one can think of something like:

swaged.invokeInSeries()
swaged.invokeOnThread(...)
...

the same reason kotlin chooses to provide basically no constructs for concurrency and instead hand that off to external libraries.

On the other hand Kotlin implements a lot of useful and convenient features that could be left to libraries. Events have proved to be a great patter in C# and their advantages are clear.


#8

The point roschlau was making is that these are questions don’t have a single anwer. It simply depends on the situation.

Furthermore:

Events are very convenient and remove a lot of boiler plate code.

This argument is valid for just about any language feauture. So it does not add much in determining whether a feature should be added. It would be interesting to know how much this is actually used. How much bugs these kind of constructs actually save.

Class interface becomes simpler. No addListener, removeListener, setListener methods.

Though that might be true for Kotlin, how would this interop with Java? Would these just be generated?

You don’t have to manually store the handlers references.

Which is the same argument as your first, saves you from boilerplate code, which is just a general statement of just about any feature.

It’s easy to invoke multiple handlers.

Again, the boilerplate argument.

Implement only the callbacks you need and not all of them like in interfaces.

We can achieve the exact same thing with just several functions.

So I think the added value of C# style events is not that significant. And, of course, I could be wrong. But for that better arguments are probably needed.


#9

IMHO, I also don’t think that it’s necessary to add compiler/language-level support for this feature, since it can be very easily implemented with the current syntax.


#10

What trigger thread? This is not a common practice I am aware of.

But the real answer is use RxJava for events and you are in control of the threading.

I disagree with that statement. I see very little in Kotlin itself that could be left to libraries. That is actually part of their philosophy. There is a lot implemented in the standard LIBRARY that could have been left to other libraries but wasn’t for some of the same reasons you gave.


#11
class Foo {

    public swaged: Event&lt;String&gt;() // declaration

    fun qux() {
        swaged += { onSwag(it) } // subscription
        swaged("coorge") // invocation   
    }

    // event handler
    fun onSwag(x: String) { ... }
}

Well with 1.1 that can now be:

swaged += this::onSwag

#12

One thing that’s hard to do in libraries (not impossible, but slightly annoying/verbose). In C#, events can only be triggered by the object on which they are defined. Effectively, an event in C# gets compiled to 3 methods on the class they are defined: a add/removeListener, and a protected trigger method. The here is the access modifier put in front of the event keyword (public event MyEventType Foo results in add/remove being public).

You can achieve the same using some Event class which has add/remove/invoke as public, implementing an IEvent interface which only has add/remove, and leave the actual Event as private, but it’s really easy to get around this with a simple cast (as opposed to having to do reflection juggling to trigger the private method on the class that owns the event in C#).


#13

“extremely basic” and naive is a correct description for this and why it shouldn’t be posted as an answer. This is not what C# has. You also need to make it thread safe (delegates are actually immutable in c#), handle unregistering, exceptions (provide get invocation list), support any number of params, equality, etc… I can tell that it’s the 1st day of evaluating kotlin for me and the first disappointment not to see something built-in for proper callbacks/observable which every single use case needs (more or less)


#14

@milky My point was not that you should just take my naive implementation and use it, this was just meant as proof that you can implement exactly what C# has (syntax- and usage-wise) using existing language features without the need for the language itself dictating The One Correct Way™ to do events.

All the things you listed have to be taken into consideration, but for a lot of them, there is no one-size-fits-all answer that will satisfy all use cases for events. That’s why I think dedicated libraries will be able to do a much better job. Just look at RxJava for example - it serves almost all use cases for events pretty well, while giving you a lot of flexibility about all the points you mentioned. There’s a reason it’s pretty ubiquitous in Android development.

Another thing to consider is the approach the Kotlin team has taken towards other language features, and the development of the language in general - coroutines come to mind in particular. They started from the desire to have some kind of asynchronicity functionality in the language, like C#'s async/await for example. But instead of just jumping ahead and building those as keywords into the language, they dug deeper into the problem core, and decided to built something much more fundamental into the language, which is coroutines. This now enables us to have C#-style async/await, but also python-style yield, and also actors, and a lot more, all without specific language keywords for any of these features. Instead, they are implemented in a dedicated library, and you can choose what approach suits you best.

This is the kind of approach I also think should be used towards events, the difference being that the language features to do it are already in place and there already are libraries that do this very well, so nothing additional has to be added.


#15

Then it’s not a valuable answer imo: the original question/post in this topic emphasizes “remove a lot of boiler plate code”, among other things. And then the answer is misleading as if it’s 4 lines of code, but it’s not for correct implementation.

I don’t understand the argument about other libraries: that could be said just about anything - then kotlin shouldn’t exist since it can be achieved with some libraries, somehow. The question is about core language support, 3rd party libraries are always there - language support is not an enforcement “one way”, it’s an option in the language. I think kotlin attracts because it promises an elegant, concise, convenient etc replacement to Java with its numerous 3rd party libraries to address the deficiencies.

“One way” is not always a bad thing either. I personally don’t want to continue wasting time writing bridges and wrappers to integrate one library/code base to another (in terms of communication, events). I’d rather focus on new problems then re-solving the same bugs and copy code…

Events, observable properties are the basic thing one needs in a project. My perfect language would provide a nice default mechanics implementation/help/recommendations for this. And 3rd party is for who wants something else, maybe later in the project with the specific requirements…

Events may be especially a must for client side, i.e. Android (my usage). Almost everything needs to be tied to UI. The way i see it, Google has been making this mistake with Android SDK for UI for years and finally got to addressing it: only recently have they started added xml binding, a notion of model/viewmodel, observable properties, but on an awkward legacy code…

For the mentioned Rx in particular: it think the problem with Rx is that it is being pitched wether it suits or not, as if it’s an answer to everything. First, it adds complexity (debugging, understanding, especially for big teams where different people jump in and “fix” stuff). It adds significant overhead and performance doesn’t compare to C# delegate implementation (which is optimized, supported at the IL level - not a library on top of …). When you have hundreds of objects and you need is hundred of observable properties (delegates), you really want it light and the language authors thought through, worked out corner cases, documented and optimized the heck out of it

coroutines are cool but irrelevant for this particular topic discussion.


#16

…in the calling code, not the implementing code. Which is why I provided a minimal implementation just to prove that eliminating any boilerplate in the calling code is possible to the same extent like in C#. I explicitly made it a point to describe my implementation as “extremely basic”, I honestly didn’t think that anyone would expect a fully fleshed-out, production ready library there.

Yes, anything Kotlin does can be achieved with pure Java somehow, but only by jumping through lots of hoops and drastically reducing readability and maintainability. In the case we’re discussing, additional language support could not give us a much more readable syntax for events than libraries written in pure Kotlin can.

You’ll do that anyway. As you yourself say in the next paragraph:

And those who want something else will be plenty, even with existing language support. There will still be lots of Rx you’d need to connect to, other Reactive Streams implementations, event busses, callback-based libraries etc. Those aren’t going away by introducing language-level support, if anything, it’s going to get worse. Reminds me of this relevant xkcd.

Nope. Well, yes, but the way it is now, those are usually things the Framework you’re working in already provides. Android has it’s OnClickListeners, JavaFX has EventHandlers. And as long as you’re not looking at Kotlin-only frameworks, none of them will use a language-level events-system unless JetBrains outdo themselves and provide excellent interop with Java for them. And even if that was the case, the chances of existing frameworks to switch over are pretty slim. So again, that would leave us with just another events-system that needs to be bridged into all the other ones already existing. I agree the existing solutions, like Android, are a mess for the most part, but language support in Kotlin won’t help that for the foreseeable future.

Yeah, I’ll give you that, performance definitely is a thing to consider with Rx. But you could easily use a much more barebones and lightweight events library and would come pretty close to a language-level implementation. Anything beyond that would be premature optimization in all but the most performance-critical applications, and at that point, you should probably be asking yourself if the JVM is the right platform to develop your application on.

While coroutines themselves are irrelevant, the approach taken in their design as a language feature is indeed very relevant, I think.

To summarize: Right tool for the right job. No point in building a 5mm crosstip screwdriver into the language, when lots of users will need a 2mm or 9mm, or even a slotted or double-square.


#17

Somebody should mention kotlin’s delegated properties in this topic, so I am. Although it is an entirely different feature from C# delegates (c# event, action), it overlaps in some requirements/usage and allows for interesting possibilities, not available in c#. Some of the built-in implementations provided by kotlin (eg Delegates.observable) may be a bit too crude (not applicable to my usage of observable properties as is – for instance, I need multiple observers) but the overall design, support of indirection (provideDelegate) seems powerful.


#19

I agree with @milky for the most part. I think it would be beneficial for the standard library to have a “typical” implementation that people can use out of the box, though. It would help C# devs adapt to Kotlin and vice versa, plus it would provide a semi-standard. To use the screw driver analogy: it would be like providing a #2 Phillips, which covers a great majority of Phillips screw head “use cases”. There will be a need for other sizes for other screws, but a man can get pretty far with just a #2. (This analogy assumes that different screw head types are different features, not alternative ways of doing the same feature. Only the different sizes refer to that.)


#20

I personally don’t like C#'s approach to event keyword whatsoever. There are some reasons for this:

  • The use of operators to add listeners is just weird and unintuitive.
  • There is 0 guarantee to the execution order of listeners, while in my experience this is really important for events (coming from Java here).
  • The internal code is very strange and rings alarm bells for me. It appears to allocate a new array each time you add an element beyond the first. What the hell? Can’t it just simply link nodes together to avoid that? Now, please don’t take my word for the latter, it might increase the array size by more than 1 each time, I just don’t recall it doing that. Regardless, it’s not a very transparent solution as to how it actually works.

With that out of the way, it would be possible for kotlin to implement its own semantics for a feature like it.
However, I do not understand how it would be better than a property of a collection type, using a specific implementation or a custom implementation, and just add stuff to that. You can still use operator overloading if you like that as well.