Figuring out how technologies are made under the hood is one of the things I love to do. Ever since I started using Blazor and going in-depth into its features, I can’t stop wondering how things are made internally.
This post is the start of a series about, you guess it, Blazor Internals. I’ll be talking about things that I find interesting in Blazor and how they work under the hood by reading the source code.
I’d also like to point to the amazing blog of Safia Abdallah, an engineer at Microsoft that works on Blazor. She has some great posts about Blazor’s internals too.
What is an EventCallback
EventCallback
(and its generic version EventCallback<T>
) are structs that are declared in a component as parameters (using [Parameter]
). Consumers of that component can “subscribe” to those parameters so that they are “notified” whenever they are triggered. You can think of them as a small / straightforward version of C# events.
@onclick
and @onmousedown
) use EventCallback
.
Usage
Let’s take as an example a Button
component:
As you can see, we declare a OnClick
parameter of type EventCallback
. We want the users of this component to be able to subscribe to the button’s click event. Whenever the button is clicked, we call OnClick.InvokeAsync()
to notify the subscriber (if there is one).
We can use it this way:
What’s interesting about it
Unlike regular C# events, EventCallback
can’t be null (since it’s a struct
) and can only have a single subscriber. This works well for Blazor since you almost always only need one subscriber (which is usually the parent).
If you paid attention to the examples above (our Button
component and its user), you will find that I declared the first handler as an async Task
and the second one as void
. This is what’s great about EventCallback
, your handlers can either be asynchronous (Task
) or not (void
).
How does it work
For example, when you subscribe to the @onclick
event, which is an EventCallback<MouseEventArgs>
, in your component (@onclick="SomeMethod"
), it will get compiled to something like this:
The same goes for the non generic version of EventCallback
, everything is created using EventCallback.Factory.Create
, which handles many types of methods:
This is how event handlers can be asynchronous or not. EventCallbackFactory
contains all these overloads for the different use cases.
Storage
The next logical question is: How does EventCallback
store the delegates when their signature isn’t the same? Easy:
All delegates (Action
, Func
, your own) are MulticastDelegate
s, which are Delegate
s. This makes it easy to handle multiple types of methods (different signatures). See MulticastDelegate Class for more information about it.
Here’s an example to better understand how it works:
As demonstrated, you can cast all delegates to MulticastDelegate
and back to their origin type.
Execution
Whenever Blazor wants to trigger the event callback, it calls EventCallback.InvokeAsync
. This calls Receiver.InvokeAsync
with a new EventCallbackWorkItem
and an argument (in case it’s the generic version). See the source code.
EventCallbackWorkItem
is the star struct here:
As you can see, EventCallbackWorkItem
executes the MulticastDelegate
by first trying to cast it to known delegates (Action
and Func
). This way the performance is the same as calling the methods directly. The fallback is to call DynamicInvoke
, which I personally don’t know when it can happen, but if it does, it’s a big performance hit.
Here’s how the receiver handles the event:
After the event is triggered, StateHasChanged
is called. If the event handler returned an incomplete Task
, StateHasChanged
is called again after the task is finished.
Some Blazor developers, including me, don’t like this automatic SHC call and prefer to have an option to disable it. I believe the Blazor team is working on it.
Conclusion
At first, you might think that some functionalities are so hard to implement and scary to understand, until you dive deep into them and it turns out to be a fun journey.
Event callbacks are a great feature that is used everywhere, it’s only fair we understand how it works under the hood.
I hope you enjoyed this post, stay tuned for the next ones!