Blazor Internals: EventCallback

Figuring out how technologies are maid 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.

All Blazor's UI events (e.g. @onclick and @onmousedown) use EventCallback.


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.


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 MulticastDelegates, which are Delegates. 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.


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.


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!

Zanid Haytam Written by:

Zanid Haytam is an enthusiastic programmer that enjoys coding, reading code, hunting bugs and writing blog posts.

comments powered by Disqus