Efficiency Unleashed: C# Source Generators and the Future of MAUI Apps

Streamlining MVVM: Code Elegance with Source Generators

Efficiency Unleashed: C# Source Generators and the Future of MAUI Apps

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:

  1. Dependency Injection:

    • Automatically generate code for registering classes or interfaces with a dependency injection container.
  2. INotifyPropertyChanged Implementation:

    • Automatically implement the INotifyPropertyChanged interface for properties, reducing boilerplate code and improving code readability in MVVM patterns.
  3. 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.
  4. 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.
  5. Builder Pattern Implementation:

    • Automatically generate builder classes for complex object construction, providing a fluent and type-safe interface for building instances of a class.
  6. 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.
  7. DTO (Data Transfer Object) Generation:

    • Automatically generate DTOs based on existing data models, facilitating data exchange between different layers of an application.
  8. 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.
  9. AutoMapper Configuration:

    • Generate AutoMapper configuration code for mapping between different data models, reducing manual configuration and ensuring consistency.
  10. Event Handling and Messaging:

    • Automatically generate code for event handling or messaging patterns, simplifying the implementation of publish-subscribe mechanisms within an application.
  11. 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.
  12. API Client Generation:

    • Automatically generate client libraries for consuming web APIs based on API specifications, reducing the manual effort to create API clients.
  13. 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:

  1. 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.

  2. Right-click on the SourceGenerator project

  3. Click Properties.

  4. Click Debug.

  5. Click Open debug launch profiles UI.

  6. Click on Delete to delete the profile shown.

  7. Click on Add

  8. Select the Roslyn component.

  9. In the Target project, select the ConsoleApp project.

  10. Close the UI.

  11. Restart Visual Studio 2022.

  12. In the debug profiles dropdown next to the Play button, select the profile that you created.

  13. Put a breakpoint in your SourceGenerator to ensure the debugger works.

  14. 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.

    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.

Did you find this article valuable?

Support Pavlo Datsiuk by becoming a sponsor. Any amount is appreciated!