Efficiency Unleashed: C# Source Generators and the Future of MAUI Apps
Streamlining MVVM: Code Elegance with Source Generators
Table of contents
- Harnessing the Power of C# Source Generators for MAUI Development
- The Importance of AOT Compilation in MAUI Development (and Xamarin, too)
- Scenarios Where C# Code Generators Shine
- The Simplest C# Source Generator Implementation
- Streamlining MVVM Implementation with C# Source Generators
- MVVM Community Toolkit
- In Conclusion
Harnessing the Power of C# Source Generators for MAUI Development
In the dynamic realm of .NET development, the rise of Source Generators, a Roslyn feature, has been noteworthy, especially with the latest .NET iterations. These generators empower developers to actively engage in the compilation cycle during builds and within IDEs like Visual Studio. Their capabilities encompass code analysis, syntax checking, and dynamic code generation during compilation. It's essential to note that Source Generators focus on crafting code rather than altering existing code. Their significance lies in the shift from runtime reflection to compile time, addressing challenges associated with ahead-of-time (AOT) compilation trimming. AOT compilation is crucial for scenarios like mobile app deployment, ensuring streamlined and efficient runtime performance. This narrative emphasizes the practical utilization of C# Source Generators in MAUI development, showcasing their pivotal role in overcoming AOT compilation challenges and enhancing development efficiency.
The Importance of AOT Compilation in MAUI Development (and Xamarin, too)
AOT compilation plays a pivotal role in .NET application development, particularly in the context of MAUI and Xamarin. It involves transforming high-level language code into native machine code before execution, boosting application performance and startup times. In the realm of MAUI, tailored for cross-platform applications, AOT compilation becomes indispensable for delivering a seamless user experience across diverse platforms. It optimizes the application for each specific platform, eliminating the need for Just-In-Time (JIT) compilation during runtime. This optimization is especially vital in resource-constrained environments like mobile devices, where efficiency and responsiveness take precedence. AOT compilation aligns seamlessly with MAUI and Xamarin development principles, enabling the creation of high-performance, platform-agnostic applications.
Scenarios Where C# Code Generators Shine
Source generation emerges as a powerful tool in various scenarios, automating repetitive tasks, enhancing code quality, and addressing specific challenges in software development. Here's a list of examples illustrating where source generation proves particularly beneficial:
Dependency Injection:
- Automatically generate code for registering classes or interfaces with a dependency injection container.
INotifyPropertyChanged Implementation:
- Automatically implement the INotifyPropertyChanged interface for properties, reducing boilerplate code and improving code readability in MVVM patterns.
Serialization and Deserialization:
- Generate code to serialize and deserialize objects to and from various formats (JSON, XML, etc.). This can be especially useful when working with complex object hierarchies.
Code Analysis and Annotation:
- Perform static code analysis and automatically annotate code with additional information or attributes based on specific criteria. This can aid in enforcing coding standards or generating documentation.
Builder Pattern Implementation:
- Automatically generate builder classes for complex object construction, providing a fluent and type-safe interface for building instances of a class.
Equals and GetHashCode Methods:
- Generate implementations for Equals and GetHashCode methods based on the properties of a class. This ensures consistency and correctness when working with collections and dictionaries.
DTO (Data Transfer Object) Generation:
- Automatically generate DTOs based on existing data models, facilitating data exchange between different layers of an application.
Aspect-Oriented Programming (AOP):
- Implement cross-cutting concerns such as logging, validation, or caching by automatically injecting code into specified methods or properties during compilation.
AutoMapper Configuration:
- Generate AutoMapper configuration code for mapping between different data models, reducing manual configuration and ensuring consistency.
Event Handling and Messaging:
- Automatically generate code for event handling or messaging patterns, simplifying the implementation of publish-subscribe mechanisms within an application.
Custom Code Annotations:
- Define custom annotations and generate corresponding code based on these annotations. This can be useful for creating domain-specific languages or enforcing specific patterns.
API Client Generation:
- Automatically generate client libraries for consuming web APIs based on API specifications, reducing the manual effort to create API clients.
Data Validation:
- Generate code for input validation based on specified rules or attributes, ensuring that data adheres to defined constraints.
The Simplest C# Source Generator Implementation
Creating the most straightforward C# source generator involves developing a custom source generator that analyzes the syntax tree of a C# program during compilation and emits additional C# code. Here's a brief overview of the steps for implementing a basic code generator:
1. Create two projects in your solution
You can find more info here
- Create the Console app project
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ClassLibrary\SourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
</Project>
- Add Program.cs to the Console app
namespace ConsoleApp;
partial class Program
{
static void Main(string[] args)
{
HelloFrom("Generated Code");
}
static partial void HelloFrom(string name);
}
- The SourceGenerator project
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
<IsRoslynComponent>true</IsRoslynComponent>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
</ItemGroup>
</Project>
- Add the source generator to the SourceGenerator project
using Microsoft.CodeAnalysis;
namespace ClassLibrary
{
[Generator]
public class HelloSourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
// Find the main method
var mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken);
// Build up the source code
string source = $@"// <auto-generated/>
using System;
namespace {mainMethod.ContainingNamespace.ToDisplayString()}
{{
public static partial class {mainMethod.ContainingType.Name}
{{
static partial void HelloFrom(string name) =>
Console.WriteLine($""Generator says: Hi from '{{name}}'"");
}}
}}
";
var typeName = mainMethod.ContainingType.Name;
// Add the source code to the compilation
context.AddSource($"{typeName}.g.cs", source);
}
public void Initialize(GeneratorInitializationContext context)
{
// No initialization required for this one
}
}
}
2. Debug configuration for the Source Generator project
At this point, you have a project that works but can't use a debugger in the source generator. To enable it, please follow the steps:
The .NET Compiler Platform SDK must be installed.
Open your Visual Studio Installer and add the .NET Compiler Platform SDK component. This component is required only for debugging purposes, so if you don't want to debug the source generator by yourself, you can skip this step.
Right-click on the SourceGenerator project
Click Properties.
Click Debug.
Click Open debug launch profiles UI.
Click on Delete to delete the profile shown.
Click on Add
Select the Roslyn component.
In the Target project, select the ConsoleApp project.
Close the UI.
Restart Visual Studio 2022.
In the debug profiles dropdown next to the Play button, select the profile that you created.
Put a breakpoint in your SourceGenerator to ensure the debugger works.
Press F5 to run your project.
Streamlining MVVM Implementation with C# Source Generators
I tried to write this article to be mobile-related, so let's discuss MVVM implementation and why we need source generators here.
The Model-View-ViewModel (MVVM) pattern is widely acclaimed for its role in producing clean, readable, and maintainable code. However, the pattern often entails a substantial amount of boilerplate code, especially when implementing the INotifyPropertyChanged interface. Traditionally, developers have had to manually implement INotifyPropertyChanged for each property in their ViewModel, resulting in verbose and error-prone code.
The typical code structure:
private string name;
public string Name
{
get => name;
set
{
if (name.Equals(value))
{
return;
}
name = value;
OnPropertyChanged();
}
}
Source Generators streamline this process significantly. By annotating properties with the [ObservableProperty] attribute, developers can automatically generate the necessary code for property change notifications.
The new, concise code for the same property:
[ObservableProperty]
private string name;
Generated observable property:
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
MVVM Community Toolkit
The MVVM Community Toolkit, formerly Microsoft.Toolkit.Mvvm stands as a contemporary and modular MVVM (Model-View-ViewModel) library integral to the .NET Community Toolkit. With a focus on platform and runtime independence, it supports .NET Standard 2.0+ and .NET 6+, ensuring compatibility across various UI frameworks like MAUI, UWP, WinForms, WPF, Xamarin, Uno, and more. Key features include flexibility in choosing components, a lightweight and self-contained structure, and a unified API surface across platforms.
Equipped with Source Generators, the MVVM Community Toolkit proves to be a potent solution. This toolkit simplifies MVVM development by providing essential types such as ObservableObject, RelayCommand, IoC, IMessenger, etc.
Vivid Examples Related to Source Generators
private string name;
[Required]
[MinLength(2)]
[MaxLength(100)]
public string Name
{
get => name;
set => SetProperty(ref name, value, true);
}
// You can use method as command in XAML <Button Command="{Binding TestCommand}"/>
[RelayCommand]
private void Test()
{
// To-Do something
}
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
private string _firstName;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
private string _lastName;
// A PropertyChanged event will trigger
// whenever there is a change in either the FirstName or LastName property
public string FullName => $"{FirstName} {LastName}";
In Conclusion
As we navigate the ever-evolving landscape of .NET, mastering Source Generators becomes crucial for developers seeking to optimize workflows and effortlessly deliver high-performance cross-platform applications.