Performances .NET MAUI 10 : XAML Source Generation, NativeAOT et Optimisations Pratiques

Explorez les optimisations clés de .NET MAUI 10 : génération de source XAML pour des vues 100x plus rapides, NativeAOT pour iOS, trimming IL, gestion mémoire et bonnes pratiques réseau et CI/CD.

Introduction : Pourquoi les Performances Comptent Vraiment dans .NET MAUI 10

Avec la sortie de .NET 10 en novembre 2025, .NET MAUI franchit un cap décisif en matière de performances. Microsoft a concentré ses efforts sur trois axes majeurs : la génération de code source XAML à la compilation, le déploiement NativeAOT pour iOS et Mac Catalyst, et une série d'optimisations transversales. Pour ceux d'entre nous qui construisons des applications mobiles multiplateformes au quotidien, ces améliorations ne sont pas simplement incrémentales — elles changent fondamentalement ce qu'on peut attendre du framework.

Honnêtement, après avoir passé pas mal de temps à tester ces nouveautés, je peux dire que le saut en performances est tangible.

Dans ce guide, on va explorer en profondeur chaque pilier d'optimisation de .NET MAUI 10, avec des exemples pratiques, des benchmarks concrets et des stratégies d'implémentation que vous pourrez appliquer immédiatement à vos projets. Que vous partiez de zéro ou que vous migriez depuis Xamarin.Forms, vous trouverez ici de quoi tirer le meilleur parti de votre application mobile.

XAML Source Generation : La Révolution de la Compilation

Comprendre le Problème Historique

Depuis les débuts de Xamarin.Forms et jusqu'aux versions précédentes de .NET MAUI, le XAML était interprété à l'exécution. Concrètement, cela signifiait que chaque fois qu'une page se chargeait, le framework devait analyser le balisage XAML, résoudre les types par réflexion, créer les instances de contrôles et établir les liaisons de données — le tout au moment de l'exécution. Ce processus introduisait une latence perceptible, surtout sur les appareils d'entrée de gamme ou lors du chargement de pages complexes avec beaucoup d'éléments imbriqués.

Le compilateur XAML (XamlC) existait déjà et effectuait une validation à la compilation, mais l'inflation réelle des vues — c'est-à-dire la transformation du XAML en arbre visuel — restait une opération d'exécution basée sur la réflexion. Si vous avez déjà eu cette sensation de « lag » en naviguant entre vos pages pendant le développement, c'est précisément ça le coupable.

Le Générateur de Source XAML dans .NET MAUI 10

Avec .NET MAUI 10, Microsoft introduit un véritable générateur de source Roslyn pour le XAML. Au lieu d'interpréter le balisage à l'exécution, le générateur crée du code C# fortement typé à la compilation. Ce code généré remplace intégralement le chemin basé sur la réflexion, éliminant ainsi une source majeure de latence.

Pour activer cette fonctionnalité, une seule ligne dans votre fichier projet suffit :

<PropertyGroup>
    <MauiXamlInflator>SourceGen</MauiXamlInflator>
</PropertyGroup>

Aussi simple que ça.

Résultats de Performance Mesurés

Les benchmarks publiés par l'équipe .NET sont franchement impressionnants :

  • Temps d'inflation des vues en Debug : jusqu'à 100 fois plus rapide (oui, vous avez bien lu — 10 000 % d'amélioration)
  • Temps d'inflation des vues en Release : 25 % plus rapide
  • Rendu UI général : environ 25 % plus rapide grâce à l'élimination de la réflexion
  • Détection d'erreurs XAML : les erreurs sont signalées à la compilation plutôt qu'à l'exécution

L'amélioration la plus spectaculaire concerne le mode Debug. Lors du développement, les temps d'inflation des vues passent de plusieurs centaines de millisecondes à quelques millisecondes. C'est le genre de changement qui fluidifie vraiment votre cycle de développement au quotidien.

Exemple Pratique : Avant et Après

Considérons une page XAML typique avec un formulaire de contact :

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:MonApp.ViewModels"
             x:Class="MonApp.Views.ContactPage">

    <ContentPage.BindingContext>
        <vm:ContactViewModel />
    </ContentPage.BindingContext>

    <ScrollView>
        <VerticalStackLayout Padding="20" Spacing="15">
            <Label Text="Formulaire de Contact"
                   FontSize="24"
                   FontAttributes="Bold"
                   HorizontalOptions="Center" />

            <Entry Placeholder="Nom complet"
                   Text="{Binding Nom}" />

            <Entry Placeholder="Adresse e-mail"
                   Text="{Binding Email}"
                   Keyboard="Email" />

            <Editor Placeholder="Votre message"
                    Text="{Binding Message}"
                    HeightRequest="150" />

            <Button Text="Envoyer"
                    Command="{Binding EnvoyerCommand}"
                    BackgroundColor="#512BD4"
                    TextColor="White" />
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

Avec le générateur de source activé, ce XAML est transformé à la compilation en code C# qui instancie directement les contrôles, configure les propriétés et établit les liaisons — sans aucune réflexion à l'exécution. Le résultat : une page qui s'affiche quasi instantanément, même en mode Debug.

Espaces de Noms XAML Globaux

.NET MAUI 10 introduit également les espaces de noms XAML globaux, et c'est une vraie bouffée d'air frais. Cette fonctionnalité réduit la verbosité en déclarant les espaces de noms une seule fois pour l'ensemble du projet :

<PropertyGroup>
    <MauiXamlGlobalNamespaces>true</MauiXamlGlobalNamespaces>
</PropertyGroup>

Combiné avec la génération de source, cela permet d'écrire du XAML plus concis tout en bénéficiant de performances optimales. Plus besoin de déclarer xmlns:local ou xmlns:vm dans chaque fichier XAML — les espaces de noms de votre projet sont automatiquement disponibles. Franchement, c'est le genre de petit détail qui fait une grosse différence au quotidien.

NativeAOT : Compilation Anticipée pour des Performances Natives

Qu'est-ce que NativeAOT ?

NativeAOT (Native Ahead-of-Time) est un mode de déploiement qui compile votre application .NET directement en code machine natif, sans passer par l'interprétation JIT (Just-In-Time) ni par le runtime Mono. Le résultat est un binaire autonome, optimisé pour la plateforme cible, qui démarre plus rapidement et consomme moins de mémoire.

Dans .NET MAUI 10, NativeAOT est disponible pour iOS, Mac Catalyst et Windows. Le support Android n'est pas encore au rendez-vous (Android utilise déjà une compilation AOT via le runtime ART), mais les travaux sont en cours pour les futures versions.

Activer NativeAOT dans Votre Projet

Pour activer NativeAOT, modifiez votre fichier .csproj comme suit :

<PropertyGroup Condition="$(TargetFramework.Contains('ios'))">
    <PublishAot>true</PublishAot>
    <MtouchLink>Full</MtouchLink>
</PropertyGroup>

<PropertyGroup Condition="$(TargetFramework.Contains('maccatalyst'))">
    <PublishAot>true</PublishAot>
</PropertyGroup>

Un point important à garder en tête : NativeAOT impose des contraintes sur certaines fonctionnalités du runtime .NET, notamment la réflexion dynamique et le chargement dynamique d'assemblies. Votre application doit être entièrement compatible avec le trimming et l'AOT.

Impact sur les Performances iOS

Les résultats sur iOS sont particulièrement marquants :

  • Taille de l'application : réduction d'environ 50 % par rapport au déploiement Mono
  • Temps de démarrage : amélioration d'environ 50 %
  • Performances à l'exécution : jusqu'à 50 % d'amélioration sur les opérations intensives en calcul
  • Consommation mémoire : empreinte réduite grâce à l'élimination du code non utilisé

Ces chiffres sont particulièrement significatifs pour les applications iOS déployées sur l'App Store, où la taille du paquet influence directement le taux de téléchargement. On le sait tous : un utilisateur hésite beaucoup plus à télécharger une app de 150 Mo qu'une app de 30 Mo.

Vérifier la Compatibilité de Votre Code

Avant d'activer NativeAOT, assurez-vous que votre code est compatible. Voici les points clés :

// ❌ Incompatible avec NativeAOT : réflexion dynamique
var type = Type.GetType("MonApp.Models.Utilisateur");
var instance = Activator.CreateInstance(type);

// ✅ Compatible avec NativeAOT : instanciation directe
var instance = new Utilisateur();

// ❌ Incompatible : chargement dynamique d'assembly
var assembly = Assembly.LoadFrom("plugin.dll");

// ✅ Compatible : référence directe au type
var service = new MonPlugin.MonService();

Pour analyser la compatibilité de votre projet, activez les analyseurs intégrés :

<PropertyGroup>
    <IsAotCompatible>true</IsAotCompatible>
    <EnableTrimAnalyzer>true</EnableTrimAnalyzer>
    <EnableAotAnalyzer>true</EnableAotAnalyzer>
</PropertyGroup>

Ces analyseurs produiront des avertissements à la compilation pour tout code potentiellement problématique avec NativeAOT. L'idée, c'est de résoudre les incompatibilités avant le déploiement, pas après (croyez-moi, c'est beaucoup moins douloureux comme ça).

Optimisation du Démarrage de l'Application

Le Pipeline de Démarrage de .NET MAUI

Le temps de démarrage est sans doute l'un des facteurs les plus critiques pour l'expérience utilisateur mobile. Un utilisateur qui doit attendre plus de 2 à 3 secondes avant de voir le premier écran risque tout simplement de fermer l'application. Dans .NET MAUI 10, plusieurs stratégies complémentaires permettent de réduire drastiquement ce temps.

Le pipeline de démarrage suit ces étapes :

  1. Initialisation du runtime .NET
  2. Chargement des assemblies
  3. Exécution de MauiProgram.CreateMauiApp()
  4. Résolution des services d'injection de dépendances
  5. Inflation de la première page XAML
  6. Rendu du premier cadre visuel

Chacune de ces étapes peut être optimisée. Voyons comment.

Stratégie 1 : Injection de Dépendances Paresseuse

L'enregistrement de nombreux services dans le conteneur DI peut allonger le démarrage plus qu'on ne le pense. Utilisez l'injection paresseuse pour les services qui ne sont pas nécessaires immédiatement :

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            });

        // Services critiques au démarrage : Transient ou Singleton
        builder.Services.AddSingleton<INavigationService, NavigationService>();
        builder.Services.AddSingleton<IAuthService, AuthService>();

        // Services non critiques : enregistrement paresseux
        builder.Services.AddSingleton<Lazy<IAnalyticsService>>(sp =>
            new Lazy<IAnalyticsService>(() => new AnalyticsService()));

        builder.Services.AddSingleton<Lazy<ISyncService>>(sp =>
            new Lazy<ISyncService>(() =>
                new SyncService(sp.GetRequiredService<IAuthService>())));

        // ViewModels
        builder.Services.AddTransient<MainViewModel>();
        builder.Services.AddTransient<ProfilViewModel>();

        return builder.Build();
    }
}

Stratégie 2 : Chargement Différé des Ressources

Les dictionnaires de ressources volumineux peuvent aussi ralentir le démarrage. L'astuce est de les diviser et de les charger à la demande :

// Dans App.xaml.cs
public partial class App : Application
{
    public App()
    {
        InitializeComponent();

        // Charger uniquement les ressources essentielles au démarrage
        Resources.MergedDictionaries.Add(new Styles.CoreStyles());

        // Différer le chargement des thèmes secondaires
        MainThread.BeginInvokeOnMainThread(async () =>
        {
            await Task.Delay(100); // Laisser la première page s'afficher
            Resources.MergedDictionaries.Add(new Styles.ThemeStyles());
            Resources.MergedDictionaries.Add(new Styles.AnimationStyles());
        });
    }
}

Stratégie 3 : Écran de Chargement Natif Optimisé

Configurez un écran de démarrage natif performant pour masquer le temps d'initialisation :

<!-- Dans le fichier .csproj -->
<ItemGroup>
    <MauiSplashScreen Include="Resources\Splash\splash.svg"
                       Color="#512BD4"
                       BaseSize="128,128" />
</ItemGroup>

Utilisez un format SVG léger pour le splash screen — ça minimise le temps de rendu initial sur toutes les plateformes.

Optimisation du Rendu et des Listes

CollectionView : Virtualisation et Recyclage

Le CollectionView est le contrôle de liste recommandé dans .NET MAUI 10. Il offre une virtualisation native qui recycle les cellules lors du défilement, réduisant considérablement la consommation mémoire pour les longues listes. Voici un exemple optimisé :

<CollectionView ItemsSource="{Binding Produits}"
                ItemSizingStrategy="MeasureFirstItem"
                SelectionMode="Single"
                SelectionChangedCommand="{Binding SelectionCommand}">

    <CollectionView.ItemsLayout>
        <LinearItemsLayout Orientation="Vertical"
                            ItemSpacing="8" />
    </CollectionView.ItemsLayout>

    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="models:Produit">
            <Border StrokeShape="RoundRectangle 8"
                    Padding="12"
                    Margin="8,0">
                <Grid ColumnDefinitions="60,*,Auto" ColumnSpacing="12">
                    <Image Source="{Binding ImageUrl}"
                           Aspect="AspectFill"
                           WidthRequest="60"
                           HeightRequest="60" />

                    <VerticalStackLayout Grid.Column="1"
                                          VerticalOptions="Center">
                        <Label Text="{Binding Nom}"
                               FontAttributes="Bold"
                               FontSize="16" />
                        <Label Text="{Binding Description}"
                               FontSize="13"
                               MaxLines="2"
                               TextColor="Gray" />
                    </VerticalStackLayout>

                    <Label Grid.Column="2"
                           Text="{Binding Prix, StringFormat='{0:C}'}"
                           FontSize="16"
                           FontAttributes="Bold"
                           VerticalOptions="Center" />
                </Grid>
            </Border>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

Les points clés d'optimisation à retenir :

  • ItemSizingStrategy="MeasureFirstItem" : au lieu de mesurer chaque élément individuellement, le framework mesure uniquement le premier et applique cette taille à tous les autres — ça réduit considérablement le travail de mise en page
  • x:DataType : active les liaisons compilées, éliminant la réflexion pour chaque cellule
  • Mise en page simple : un Grid plat plutôt que des layouts imbriqués réduit les passes de mesure et d'arrangement

Optimisation des Images

Les images sont souvent le premier goulot d'étranglement des performances dans une application mobile (et on a tendance à sous-estimer leur impact). .NET MAUI 10 offre plusieurs mécanismes pour les gérer correctement :

// Configuration du cache d'images dans MauiProgram.cs
builder.ConfigureImageSources(images =>
{
    // Le cache HTTP par défaut gère déjà la mise en cache
    // Pour un contrôle plus fin, utilisez un gestionnaire personnalisé
});

// Dans le XAML : dimensionner correctement les images
// ❌ Mauvaise pratique : charger une image de 2000x2000 dans un espace de 60x60
<Image Source="grande_image.jpg"
       WidthRequest="60"
       HeightRequest="60" />

// ✅ Bonne pratique : fournir des images à la bonne résolution
// Placez vos images dans Resources/Images avec les suffixes appropriés
// image.png (1x), [email protected] (2x), [email protected] (3x)

Gestion Mémoire et Profilage

Identifier les Fuites Mémoire

Les fuites mémoire sont un problème récurrent dans les applications mobiles, et .NET MAUI ne fait pas exception. La bonne nouvelle, c'est que .NET MAUI 10 améliore les outils de diagnostic pour les détecter plus facilement. Voici les patterns courants à éviter :

// ❌ Fuite mémoire : abonnement à un événement sans désabonnement
public class MaPage : ContentPage
{
    protected override void OnAppearing()
    {
        base.OnAppearing();
        MessagingCenter.Subscribe<App, string>(this, "Notification",
            (sender, data) => TraiterNotification(data));
    }

    // Le désabonnement est manquant !
}

// ✅ Correct : utiliser WeakReferenceMessenger de CommunityToolkit
public partial class MaPage : ContentPage
{
    protected override void OnAppearing()
    {
        base.OnAppearing();
        WeakReferenceMessenger.Default.Register<NotificationMessage>(this,
            (recipient, message) => TraiterNotification(message.Value));
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();
        WeakReferenceMessenger.Default.Unregister<NotificationMessage>(this);
    }
}

J'ai vu tellement de projets souffrir de ce genre de fuites... Le passage à WeakReferenceMessenger est vraiment l'un des premiers réflexes à avoir.

Profilage avec dotnet-trace et dotnet-gcdump

Pour profiler votre application sur un appareil réel, les outils de diagnostic .NET sont vos meilleurs alliés :

# Installer les outils de diagnostic
dotnet tool install -g dotnet-trace
dotnet tool install -g dotnet-gcdump

# Capturer une trace de performance (sur simulateur iOS ou émulateur Android)
dotnet trace collect --process-id <PID> --providers Microsoft-DotNETRuntimePrivate

# Capturer un snapshot mémoire
dotnet gcdump collect --process-id <PID> --output app_memory.gcdump

Analysez ensuite les résultats dans Visual Studio ou avec PerfView pour identifier les allocations excessives, les objets non collectés et les chemins de code lents.

IL Trimming : Réduire la Taille de l'Application

Configuration du Trimming

Le trimming supprime le code IL non utilisé de vos assemblies, réduisant la taille finale de l'application. Dans .NET MAUI 10, le trimming est plus intelligent — il supprime davantage de code mort tout en évitant les faux positifs :

<PropertyGroup>
    <!-- Activer le trimming complet -->
    <PublishTrimmed>true</PublishTrimmed>
    <TrimMode>full</TrimMode>

    <!-- Activer les avertissements de trimming -->
    <SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>

    <!-- Combiner avec NativeAOT pour un effet maximal -->
    <PublishAot>true</PublishAot>
</PropertyGroup>

Préserver le Code Nécessaire

Si certaines parties de votre code sont utilisées par réflexion (la sérialisation JSON, typiquement), il faut indiquer au trimmer de les préserver :

// Utiliser les attributs de préservation
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public class MonModele
{
    public string Nom { get; set; }
    public int Age { get; set; }
}

// Ou mieux : utiliser les Source Generators de System.Text.Json
[JsonSerializable(typeof(MonModele))]
[JsonSerializable(typeof(List<MonModele>))]
public partial class AppJsonContext : JsonSerializerContext { }

// Utilisation dans le code
var json = JsonSerializer.Serialize(monObjet, AppJsonContext.Default.MonModele);
var objet = JsonSerializer.Deserialize(json, AppJsonContext.Default.MonModele);

L'utilisation des générateurs de source pour la sérialisation JSON est doublement bénéfique : c'est compatible avec NativeAOT et le trimming, et ça offre de meilleures performances que la sérialisation basée sur la réflexion. Un vrai combo gagnant.

Blazor Hybrid : Performances des Applications Web-Native

Optimiser le WebView dans les Applications Hybrides

Si vous utilisez Blazor Hybrid avec .NET MAUI, le composant BlazorWebView introduit un runtime web à l'intérieur de votre application native. Voici comment optimiser ses performances :

// Configuration optimisée du BlazorWebView
public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureMauiHandlers(handlers =>
            {
                // Personnaliser le handler BlazorWebView pour optimiser
                handlers.AddHandler<BlazorWebView, BlazorWebViewHandler>();
            });

        // Enregistrer les services Blazor
        builder.Services.AddMauiBlazorWebView();

#if DEBUG
        builder.Services.AddBlazorWebViewDeveloperTools();
#endif

        return builder.Build();
    }
}

Bonnes Pratiques pour les Composants Razor

Dans une application Blazor Hybrid, les performances de rendu des composants Razor ont un impact direct sur la fluidité de l'interface. Quelques bonnes pratiques essentielles :

@* ✅ Bonne pratique : utiliser ShouldRender pour éviter les re-rendus inutiles *@
@code {
    private bool _devraitRendre = true;

    protected override bool ShouldRender() => _devraitRendre;

    private void MettreAJourDonnees()
    {
        // Effectuer la mise à jour
        _devraitRendre = true;
        StateHasChanged();
        _devraitRendre = false;
    }
}

@* ✅ Bonne pratique : virtualiser les longues listes *@
<Virtualize Items="@produits" Context="produit">
    <div class="carte-produit">
        <h3>@produit.Nom</h3>
        <p>@produit.Prix.ToString("C")</p>
    </div>
</Virtualize>

Quand Utiliser les Contrôles Natifs vs. Blazor

Un piège courant dans les applications Blazor Hybrid, c'est de vouloir tout rendre en HTML. Pour les meilleures performances, privilégiez les contrôles natifs .NET MAUI pour :

  • Les listes défilantes : CollectionView natif surpasse largement les listes HTML virtualisées
  • La navigation : Shell ou NavigationPage offrent des transitions natives fluides
  • Les barres d'outils : les ToolbarItem natifs respectent les conventions de la plateforme
  • Les formulaires simples : les contrôles natifs Entry, Picker, DatePicker offrent une meilleure intégration avec le clavier

Réservez Blazor pour les écrans riches en contenu, les tableaux de bord complexes et les interfaces qui bénéficient vraiment du HTML/CSS pour la mise en forme.

Benchmarking et Mesure : Quantifier Vos Améliorations

Mettre en Place des Benchmarks Automatisés

Pour mesurer objectivement l'impact de vos optimisations, utilisez BenchmarkDotNet pour les benchmarks de code et les outils de profilage de plateforme pour les métriques applicatives :

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser]
public class SerializationBenchmarks
{
    private List<Produit> _produits;

    [GlobalSetup]
    public void Setup()
    {
        _produits = Enumerable.Range(0, 1000)
            .Select(i => new Produit
            {
                Id = i,
                Nom = $"Produit {i}",
                Prix = i * 9.99m
            }).ToList();
    }

    [Benchmark(Baseline = true)]
    public string Serialisation_Reflexion()
    {
        return JsonSerializer.Serialize(_produits);
    }

    [Benchmark]
    public string Serialisation_SourceGen()
    {
        return JsonSerializer.Serialize(_produits,
            AppJsonContext.Default.ListProduit);
    }
}

Métriques Clés à Surveiller

Définissez des seuils de performance pour votre application et surveillez-les en continu. Voici les objectifs que je recommande :

  • Temps de démarrage froid : objectif < 2 secondes sur un appareil milieu de gamme
  • Temps de navigation entre pages : objectif < 300 ms
  • Fréquence d'images lors du défilement : objectif constant à 60 FPS
  • Consommation mémoire au repos : objectif < 100 Mo pour une application standard
  • Taille du paquet installé : objectif < 30 Mo pour une application de contenu

Ce ne sont pas des règles absolues, mais si vous dépassez ces seuils de manière significative, il y a probablement des optimisations à explorer.

Optimisation des Requêtes Réseau et du Stockage Local

HttpClient et Gestion Efficace des Connexions

Les applications mobiles dépendent énormément des communications réseau. Et une mauvaise gestion de HttpClient reste l'une des erreurs les plus fréquentes — elle impacte à la fois les performances et la stabilité. Dans .NET MAUI 10, l'approche recommandée est d'utiliser IHttpClientFactory avec les handlers natifs de chaque plateforme :

// Configuration dans MauiProgram.cs
builder.Services.AddHttpClient("ApiClient", client =>
{
    client.BaseAddress = new Uri("https://api.monapp.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.Timeout = TimeSpan.FromSeconds(30);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
#if ANDROID
    return new AndroidMessageHandler
    {
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
    };
#elif IOS
    return new NSUrlSessionHandler
    {
        AllowAutoRedirect = true
    };
#else
    return new HttpClientHandler
    {
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
    };
#endif
});

// Utilisation dans un service
public class ApiService
{
    private readonly IHttpClientFactory _httpClientFactory;

    public ApiService(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task<List<Produit>> ObtenirProduitsAsync()
    {
        var client = _httpClientFactory.CreateClient("ApiClient");
        var response = await client.GetAsync("produits");
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadFromJsonAsync(
            AppJsonContext.Default.ListProduit);
    }
}

L'utilisation de IHttpClientFactory garantit une gestion correcte du pool de connexions, évitant le fameux problème d'épuisement des sockets qui survient quand on crée et dispose des instances de HttpClient à répétition. Les handlers natifs (AndroidMessageHandler et NSUrlSessionHandler) offrent de meilleures performances et une intégration plus fine avec les piles réseau de chaque plateforme.

Mise en Cache Intelligente des Données

La mise en cache locale des données API réduit la latence perçue et améliore l'expérience hors ligne. Voici un pattern de cache à deux niveaux que j'utilise souvent dans mes projets .NET MAUI :

public class CacheService<T> where T : class
{
    private readonly IFileSystem _fileSystem;
    private readonly MemoryCache _memoryCache;
    private readonly JsonSerializerOptions _jsonOptions;

    public CacheService(IFileSystem fileSystem)
    {
        _fileSystem = fileSystem;
        _memoryCache = new MemoryCache(new MemoryCacheOptions
        {
            SizeLimit = 50 // Maximum 50 entrées en mémoire
        });
    }

    public async Task<T> ObtenirOuChargerAsync(
        string cle,
        Func<Task<T>> chargeur,
        TimeSpan dureeCache)
    {
        // Niveau 1 : cache mémoire
        if (_memoryCache.TryGetValue(cle, out T valeurCache))
            return valeurCache;

        // Niveau 2 : cache fichier
        var cheminFichier = Path.Combine(
            _fileSystem.CacheDirectory, $"{cle}.json");

        if (File.Exists(cheminFichier))
        {
            var infoFichier = new FileInfo(cheminFichier);
            if (DateTime.UtcNow - infoFichier.LastWriteTimeUtc < dureeCache)
            {
                var jsonCache = await File.ReadAllTextAsync(cheminFichier);
                var donnees = JsonSerializer.Deserialize<T>(jsonCache);
                _memoryCache.Set(cle, donnees, new MemoryCacheEntryOptions
                {
                    Size = 1,
                    AbsoluteExpirationRelativeToNow = dureeCache
                });
                return donnees;
            }
        }

        // Aucun cache valide : charger depuis la source
        var resultat = await chargeur();

        // Mettre en cache aux deux niveaux
        _memoryCache.Set(cle, resultat, new MemoryCacheEntryOptions
        {
            Size = 1,
            AbsoluteExpirationRelativeToNow = dureeCache
        });

        var json = JsonSerializer.Serialize(resultat);
        await File.WriteAllTextAsync(cheminFichier, json);

        return resultat;
    }
}

Ce pattern mémoire + fichier est particulièrement efficace sur mobile. Le cache mémoire évite la désérialisation répétée, tandis que le cache fichier assure une expérience hors ligne fluide. Et cerise sur le gâteau : combiné avec le générateur de source JSON, c'est entièrement compatible avec NativeAOT et le trimming.

SQLite : Bonnes Pratiques de Performance

Pour le stockage local structuré, SQLite reste le choix de prédilection dans .NET MAUI. Voici les optimisations essentielles :

// Configuration optimisée de SQLite avec sqlite-net-pcl
public class DatabaseService
{
    private SQLiteAsyncConnection _database;

    async Task InitialiserAsync()
    {
        if (_database != null) return;

        var cheminBd = Path.Combine(
            FileSystem.AppDataDirectory, "monapp.db3");

        _database = new SQLiteAsyncConnection(cheminBd,
            SQLiteOpenFlags.ReadWrite |
            SQLiteOpenFlags.Create |
            SQLiteOpenFlags.SharedCache);

        // Activer le mode WAL pour de meilleures performances concurrentes
        await _database.ExecuteAsync("PRAGMA journal_mode=WAL");

        // Optimiser la taille du cache en pages (défaut : 2000)
        await _database.ExecuteAsync("PRAGMA cache_size=-8000"); // 8 Mo

        // Créer les tables
        await _database.CreateTableAsync<Produit>();
        await _database.CreateTableAsync<Commande>();
    }

    // Insertion par lot pour les grandes quantités de données
    public async Task InsererProduitsAsync(List<Produit> produits)
    {
        await InitialiserAsync();
        await _database.RunInTransactionAsync(conn =>
        {
            conn.InsertAll(produits);
        });
    }
}

Les points clés pour SQLite sur mobile : activez le mode WAL pour améliorer les performances en lecture concurrente, utilisez les transactions pour les insertions par lot, et dimensionnez correctement le cache mémoire en fonction de vos données. Ça a l'air basique dit comme ça, mais vous seriez surpris du nombre de projets qui passent à côté de ces optimisations.

Intégration Continue et Pipeline de Performance

Tests de Performance Automatisés dans la CI/CD

L'optimisation des performances ne devrait pas être un effort ponctuel. Intégrez des tests de performance dans votre pipeline CI/CD pour détecter les régressions automatiquement :

public class PerformanceTests
{
    [Fact]
    public void Navigation_VersPageProduit_DoitEtreRapide()
    {
        // Arrange
        var chrono = Stopwatch.StartNew();

        // Act
        var page = new ProduitsPage();
        page.BindingContext = new ProduitsViewModel(
            new MockApiService(),
            new MockNavigationService());

        chrono.Stop();

        // Assert : l'instanciation ne doit pas dépasser 100ms
        Assert.True(chrono.ElapsedMilliseconds < 100,
            $"L'instanciation de ProduitsPage a pris {chrono.ElapsedMilliseconds}ms");
    }

    [Fact]
    public async Task Serialisation_1000Produits_DoitEtreRapide()
    {
        // Arrange
        var produits = Enumerable.Range(0, 1000)
            .Select(i => new Produit { Id = i, Nom = $"Produit {i}" })
            .ToList();

        // Act
        var chrono = Stopwatch.StartNew();
        var json = JsonSerializer.Serialize(produits,
            AppJsonContext.Default.ListProduit);
        chrono.Stop();

        // Assert : la sérialisation de 1000 objets doit prendre moins de 50ms
        Assert.True(chrono.ElapsedMilliseconds < 50,
            $"La sérialisation a pris {chrono.ElapsedMilliseconds}ms");
    }
}

En intégrant ces tests dans votre pipeline GitHub Actions ou Azure DevOps, vous recevrez une alerte automatique si une modification du code dégrade les performances. C'est tellement plus efficace que de découvrir les problèmes en production.

Surveillance de la Taille du Paquet

La taille de l'application compilée est un indicateur souvent négligé mais crucial. Ajoutez un vérificateur de taille dans votre pipeline de build :

# Script de vérification de taille dans le pipeline CI
# verifier_taille.sh

TAILLE_MAX_MO=35
CHEMIN_APK="bin/Release/net10.0-android/monapp.apk"

TAILLE=$(du -m "$CHEMIN_APK" | cut -f1)

if [ "$TAILLE" -gt "$TAILLE_MAX_MO" ]; then
    echo "⚠️ ALERTE : La taille de l'APK ($TAILLE Mo) dépasse le seuil de $TAILLE_MAX_MO Mo"
    exit 1
else
    echo "✅ Taille de l'APK : $TAILLE Mo (seuil : $TAILLE_MAX_MO Mo)"
fi

Surveillez les tendances de taille au fil du temps. Une augmentation progressive peut indiquer l'ajout de dépendances inutiles ou de ressources mal optimisées qui, prises individuellement, semblent anodines mais dont l'effet cumulé finit par peser.

Checklist d'Optimisation pour .NET MAUI 10

Pour terminer, voici une checklist que vous pouvez garder sous le coude et cocher au fur et à mesure :

  1. Compilation XAML
    • Activer MauiXamlInflator en mode SourceGen
    • Activer les espaces de noms XAML globaux
    • Utiliser x:DataType sur tous les DataTemplate
    • Compiler les bindings avec x:DataType
  2. Déploiement
    • Activer NativeAOT pour iOS et Mac Catalyst
    • Activer le trimming complet
    • Vérifier la compatibilité AOT avec les analyseurs
    • Utiliser les Source Generators pour la sérialisation JSON
  3. Rendu UI
    • Utiliser CollectionView au lieu de ListView
    • Définir ItemSizingStrategy="MeasureFirstItem"
    • Éviter les layouts profondément imbriqués
    • Dimensionner les images à la résolution d'affichage
  4. Mémoire
    • Utiliser WeakReferenceMessenger pour la messagerie
    • Se désabonner de tous les événements dans OnDisappearing
    • Profiler régulièrement avec dotnet-gcdump
  5. Démarrage
    • Différer l'initialisation des services non critiques
    • Charger les ressources secondaires de manière asynchrone
    • Configurer un splash screen natif léger

Conclusion : Un Écosystème Qui a Vraiment Mûri

.NET MAUI 10 marque un vrai tournant dans la maturité du framework multiplateforme de Microsoft. La combinaison de la génération de source XAML, du déploiement NativeAOT et des nombreuses optimisations du runtime offre des performances qui rivalisent sérieusement avec les applications natives en Swift ou Kotlin.

Les chiffres parlent d'eux-mêmes : des vues jusqu'à 100 fois plus rapides en Debug, des applications iOS 50 % plus légères... Ce ne sont pas des promesses marketing, ce sont des gains concrets et mesurables.

Mon conseil : adoptez une approche progressive. Commencez par activer la génération de source XAML, puis vérifiez la compatibilité de votre code avec NativeAOT, et enfin configurez le trimming complet. Chaque étape apporte des bénéfices mesurables et cumulatifs.

.NET 10 est une version LTS avec trois années de support, ce qui en fait un choix solide pour les projets d'entreprise à long terme. Les équipes qui investissent dans l'optimisation des performances dès maintenant se donnent un avantage concurrentiel réel sur un marché mobile qui ne pardonne pas la lenteur.

À propos de l'auteur Editorial Team

Our team of expert writers and editors.