Visual Studio 2022 and .NET 6 introduced .NET Hot Reload, the ability to continue coding C# while debugging your application, and use your changes without stopping. If you've used Edit and Continue, this probably feels similar.
- Start debugging your app
- Add or edit some C#
- Click the "Hot Reload" button in Visual Studio
- Re-trigger the code path to exercise your new code
Most of the time in .NET MAUI we (I) use XAML to declare UI, and XAML Hot Reload to avoid repetitive stopping and starting. This can work really well. Anytime you make a XAML edit, as long as the XAML compiler indicates your edit is valid code, the change is shipped to the running app AND the UI is updated while retaining state.
There are many cases when XAML requires me to stop and restart my session, thus breaking my flow. Remaining completely in C# drastically improves this situation.
Marrying C# UI and .NET Hot Reload
When you edit UI code in your C#, you must then do something to run that code again and see the impact of your change. That can become tiresome. Here's what I do.
I like the pattern of placing my UI construction in a Build
method. This method sets the ContentPage.Content
and is called in the page's OnNavigatedTo
method when hosted within Shell
or a NavigationPage
.
void Build() => Content =
new Grid {
};
protected override void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
Build();
}
Great! But this method is only going to get called once. I could navigate away from that page and come back. Or I could add a gesture to trigger the method manually. That gets old quickly.
What if I could make sure the Build()
method anytime I triggered a hot reload, similar to XAML Hot Reload?
Turns out I can! Anytime .NET Hot Reload executes, a metadata change is triggered, and I can hook into that. To do this, I add a HotReloadService.cs to my project.
#if DEBUG
[assembly: System.Reflection.Metadata.MetadataUpdateHandlerAttribute(typeof(YourAppNamespace.HotReloadService))]
namespace YourAppNamespace {
public static class HotReloadService
{
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public static event Action<Type[]?>? UpdateApplicationEvent;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
internal static void ClearCache(Type[]? types) { }
internal static void UpdateApplication(Type[]? types) {
UpdateApplicationEvent?.Invoke(types);
}
}
}
#endif
Anytime a change happens, the app will now dispatch an event. In the file I'm currently working on, where I want to re-execute the build, I handle the event.
protected override void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
Build();
#if DEBUG
HotReloadService.UpdateApplicationEvent += ReloadUI;
#endif
}
protected override void OnNavigatedFrom(NavigatedFromEventArgs args)
{
base.OnNavigatedFrom(args);
#if DEBUG
HotReloadService.UpdateApplicationEvent -= ReloadUI;
#endif
}
private void ReloadUI(Type[] obj)
{
MainThread.BeginInvokeOnMainThread(() =>
{
Build();
});
}
Now give that a try! Make a change to your C# in that page, and hit the Hot Reload fire button (or if you're like me, set hot reload to execute on save). Boom!
XAML Hot Reload must be enabled to leverage this event since it works over the same tooling service. If you disable XAML Hot Reload, then your C# code will reload, but you won't receive the event that you have wired up now to trigger a UI rebuild.
I have found very few scenarios so far when I have to actually stop and restart my debugging session. As you can see in this video, I even add a new class file without stopping.