Améliorer les performances de l’application
Les performances médiocres de l’application se présentent de nombreuses façons. Il peut rendre une application qui ne répond pas, peut provoquer un défilement lent et peut réduire la durée de vie de la batterie de l’appareil. Toutefois, l’optimisation des performances implique davantage de choses que l’implémentation d’un code efficace. L’expérience utilisateur des performances des applications doit également être prise en compte. Par exemple, pour contribuer à améliorer l’expérience utilisateur, vous devez vérifier que les opérations s’exécutent sans empêcher l’utilisateur d’effectuer d’autres activités.
Il existe de nombreuses techniques pour augmenter les performances et les performances perçues, des applications .NET multi-plateforme App UI (.NET MAUI). Collectivement, ces techniques peuvent réduire considérablement la quantité de travail effectuée par un processeur et la quantité de mémoire consommée par une application.
Utiliser un profileur
Lors du développement d’une application, il est important de ne tenter d’optimiser le code qu’une fois qu’elle a été profilée. Le profilage est une technique qui permet de déterminer les endroits où les optimisations de code seront les plus efficaces pour réduire les problèmes de performances. Le profileur suit l’utilisation de la mémoire de l’application et enregistre l’heure d’exécution des méthodes dans l’application. Ces données permettent de parcourir les chemins d’exécution de l’application et le coût d’exécution du code, afin que les meilleures opportunités d’optimisation puissent être découvertes.
Les applications .NET MAUI peuvent être profilées à l’aide de dotnet-trace
sur Android, iOS et Mac, et de PerfView sur Windows. Pour plus d’informations, consultez Profilage des applications .NET MAUI.
Les bonnes pratiques suivantes sont recommandées lorsque vous profilez une application :
- Évitez de profiler une application dans un simulateur, car le simulateur peut fausser les performances de l’application.
- Dans l’idéal, le profilage doit être effectué sur plusieurs types d’appareils. En effet, les performances mesurées sur un appareil ne seront pas forcément les mêmes sur un autre appareil. Cependant, le profilage doit au moins être effectué sur un appareil dont les spécifications anticipées sont les moins élevées.
- Fermez toutes les autres applications pour vous assurer que l’impact total de l’application profilée est mesuré, plutôt que les autres applications.
Utiliser des liaisons compilées
Les liaisons compilées améliorent les performances de liaison de données dans les applications .NET MAUI en résolvant les expressions de liaison au moment de la compilation, plutôt qu’au moment de l’exécution avec réflexion. La compilation d’une expression de liaison génère du code compilé qui résout généralement une liaison 8 à 20 fois plus rapidement qu’avec une liaison classique. Pour plus d’informations, consultez Liaisons compilées.
Réduire les liaisons inutiles
N’utilisez pas de liaisons pour le contenu qui peut être aisément défini de manière statique. Il n’existe aucun avantage à lier des données qui n’ont pas besoin de l’être, car les liaisons ne sont pas rentables. Par exemple, le paramètre Button.Text = "Accept"
a moins de surcharge que la liaison Button.Text à une propriété string
viewmodel avec la valeur « Accepter ».
Choisir la disposition correcte
Une disposition qui est capable d’afficher plusieurs enfants, mais qui n’en a qu’un seul, est inutile. Par exemple, l’exemple suivant montre un VerticalStackLayout avec un enfant unique :
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<VerticalStackLayout>
<Image Source="waterfront.jpg" />
</VerticalStackLayout>
</ContentPage>
Il s’agit d’un gaspillage et l’élément VerticalStackLayout doit être supprimé, comme illustré dans l’exemple suivant :
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<Image Source="waterfront.jpg" />
</ContentPage>
En outre, n’essayez pas de reproduire l’apparence d’une disposition spécifique à l’aide de combinaisons d’autres dispositions, car cela aboutirait à des calculs de dispositions inutiles. Par exemple, ne tentez pas de reproduire une disposition Grid à l’aide d’une combinaison d’éléments HorizontalStackLayout. L’exemple suivant vous montre un exemple de cette mauvaise pratique :
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<VerticalStackLayout>
<HorizontalStackLayout>
<Label Text="Name:" />
<Entry Placeholder="Enter your name" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="Age:" />
<Entry Placeholder="Enter your age" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="Occupation:" />
<Entry Placeholder="Enter your occupation" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="Address:" />
<Entry Placeholder="Enter your address" />
</HorizontalStackLayout>
</VerticalStackLayout>
</ContentPage>
Cela ne sert à rien, car des calculs de dispositions inutiles sont effectués. Au lieu de cela, la disposition souhaitée peut être améliorée à l’aide d’un Grid, comme illustré dans l’exemple suivant :
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<Grid ColumnDefinitions="100,*"
RowDefinitions="30,30,30,30">
<Label Text="Name:" />
<Entry Grid.Column="1"
Placeholder="Enter your name" />
<Label Grid.Row="1"
Text="Age:" />
<Entry Grid.Row="1"
Grid.Column="1"
Placeholder="Enter your age" />
<Label Grid.Row="2"
Text="Occupation:" />
<Entry Grid.Row="2"
Grid.Column="1"
Placeholder="Enter your occupation" />
<Label Grid.Row="3"
Text="Address:" />
<Entry Grid.Row="3"
Grid.Column="1"
Placeholder="Enter your address" />
</Grid>
</ContentPage>
Optimiser les ressources d’images
Les images sont certaines des ressources les plus coûteuses que les applications utilisent et sont souvent capturées à des résolutions élevées. Bien que cela crée des images dynamiques pleines de détails, les applications qui affichent ces images nécessitent généralement davantage d’utilisation du processeur pour décoder l’image et plus de mémoire pour stocker l’image décodée. Le décodage d’une image haute résolution dans la mémoire peut être vu comme un gaspillage de ressources lorsque l’on sait que l’image va être réduite pour son affichage dans l’application. Au lieu de cela, réduisez l’utilisation du processeur et l’empreinte mémoire en créant des versions d’images stockées proches des tailles d’affichage prédites. Par exemple, une image qui doit s’afficher dans une liste aura probablement besoin d’une résolution inférieure à celle d’une image devant s’afficher en plein écran.
En outre, les images ne doivent être créées qu’une fois requises et publiées dès que l’application ne les requiert plus. Par exemple, si une application affiche une image en lisant ses données à partir d’un flux, assurez-vous que le flux est créé uniquement lorsqu’il est nécessaire et vérifiez que le flux est libéré lorsqu’il n’est plus nécessaire. Pour cela, créez le flux quand la page est créée ou que l’événement Page.Appearing se déclenche, puis supprimez-le quand l’événement Page.Disappearing se déclenche.
Lors du téléchargement d’une image pour l’affichage avec la méthode ImageSource.FromUri(Uri), vérifiez que l’image téléchargée est mise en cache pendant une durée appropriée. Pour plus d’informations, consultez l’article Mise en cache de l’image.
Réduire la taille de l’arborescence d’éléments visuels
La réduction du nombre d’éléments dans une page accélère l’affichage de la page. Pour ce faire, il existe deux techniques principales. La première consiste à masquer les éléments qui ne sont pas visibles. La propriété IsVisible de chaque élément détermine si l’élément doit faire partie de l’arborescence d’éléments visuels ou pas. Par conséquent, si un élément n’est pas visible parce qu’il est masqué par d’autres éléments, supprimez l’élément ou affectez à sa propriété IsVisible
la valeur false
.
La seconde technique consiste à supprimer les éléments inutiles. Par exemple, la mise en page suivante montre une mise en page contenant plusieurs éléments Label :
<VerticalStackLayout>
<VerticalStackLayout Padding="20,20,0,0">
<Label Text="Hello" />
</VerticalStackLayout>
<VerticalStackLayout Padding="20,20,0,0">
<Label Text="Welcome to the App!" />
</VerticalStackLayout>
<VerticalStackLayout Padding="20,20,0,0">
<Label Text="Downloading Data..." />
</VerticalStackLayout>
</VerticalStackLayout>
La même mise en page peut être conservée avec un nombre d’éléments réduit, comme illustré dans l’exemple suivant :
<VerticalStackLayout Padding="20,35,20,20"
Spacing="25">
<Label Text="Hello" />
<Label Text="Welcome to the App!" />
<Label Text="Downloading Data..." />
</VerticalStackLayout>
Réduire la taille du dictionnaire de ressources de l’application
Toutes les ressources utilisées dans toute l’application doivent être stockées dans le dictionnaire de ressources de l’application pour éviter la duplication. Cela permet de réduire la quantité de XAML qui doit être analysée dans l’ensemble de l’application. L’exemple suivant montre la ressource HeadingLabelStyle
, qui est utilisée à l’échelle de l’application, et elle est donc définie dans le dictionnaire de ressources de l’application :
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.App">
<Application.Resources>
<Style x:Key="HeadingLabelStyle"
TargetType="Label">
<Setter Property="HorizontalOptions"
Value="Center" />
<Setter Property="FontSize"
Value="Large" />
<Setter Property="TextColor"
Value="Red" />
</Style>
</Application.Resources>
</Application>
Toutefois, le code XAML spécifique à une page ne doit pas être inclus dans le dictionnaire de ressources de l’application, car les ressources seront ensuite analysées au démarrage de l’application au lieu d’être requises par une page. Si une ressource est utilisée par une page qui n’est pas la page de démarrage, elle doit être placée dans le dictionnaire de ressources de cette page, ce qui permet de réduire le code XAML analysé au démarrage de l’application. L’exemple suivant montre la ressource HeadingLabelStyle
, qui se trouve uniquement sur une seule page, et est donc définie dans le dictionnaire de ressources de la page :
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<ContentPage.Resources>
<Style x:Key="HeadingLabelStyle"
TargetType="Label">
<Setter Property="HorizontalOptions"
Value="Center" />
<Setter Property="FontSize"
Value="Large" />
<Setter Property="TextColor"
Value="Red" />
</Style>
</ContentPage.Resources>
...
</ContentPage>
Pour plus d’informations sur les ressources d’application, consultez Style des applications à l’aide de XAML.
Réduisez la taille de l'application
Lorsque .NET MAUI génère votre application, un éditeur de liens appelé ILLink peut être utilisé pour réduire la taille globale de l’application. ILLink réduit la taille en analysant le code intermédiaire produit par le compilateur. Il supprime les méthodes, propriétés, champs, événements, structs et classes inutilisés pour produire une application qui contient uniquement du code et des dépendances d’assembly nécessaires pour exécuter l’application.
Pour plus d’informations sur la configuration du comportement de l’éditeur de liens, consultez Liaison d’une application Android, Liaison d’une application iOS etLiaison d’une application Mac Catalyst.
Réduire la période d’activation de l’application
Toutes les applications ont une période d’activation, c’est-à-dire entre le démarrage de l’application et le moment où l’application est prête à être utilisée. Cette période d’activation fournit aux utilisateurs leur première impression de l’application, et il est donc important de réduire la période d’activation et la perception de l’utilisateur, afin qu’ils obtiennent une première impression favorable de l’application.
Avant qu’une application affiche son interface utilisateur initiale, elle doit fournir un écran de démarrage pour indiquer à l’utilisateur que l’application démarre. Si l’application ne peut pas afficher rapidement son interface utilisateur initiale, l’écran de démarrage doit être utilisé pour informer l’utilisateur de la progression pendant la période d’activation, afin d’offrir une réassurance que l’application n’a pas suspendu. Pour cela, vous pouvez utiliser une barre de progression ou un autre contrôle similaire.
Pendant la période d’activation, les applications exécutent une logique d’activation, qui inclut souvent le chargement et le traitement des ressources. Pour réduire la période d’activation, empaquetez les ressources nécessaires dans l’application, au lieu de les récupérer à distance. Par exemple, dans certains cas, il peut être judicieux de charger des données d’espace réservé stockées localement pendant la période d’activation. Ensuite, une fois que l’interface utilisateur initiale est affichée et que l’utilisateur peut interagir avec l’application, les données d’espace réservé peuvent être remplacées progressivement à partir d’une source distante. En outre, la logique d’activation de l’application ne doit effectuer que le travail nécessaire pour permettre à l’utilisateur de commencer à utiliser l’application. Il peut être utile de différer le chargement des assemblys supplémentaires, car les assemblys sont chargés lors de leur première utilisation.
Choisir un conteneur d’injection de dépendances avec précaution
Les conteneurs d’injection de dépendances introduisent des contraintes de performances supplémentaires dans les applications mobiles. L’inscription et la résolution des types avec un conteneur ont un coût en termes de performances en raison de l’utilisation par le conteneur de la réflexion pour créer chaque type, et plus particulièrement si les dépendances sont reconstruites pour chaque navigation entre les pages de l’application. S’il existe de nombreuses dépendances ou des dépendances profondes, le coût de création peut augmenter de manière significative. En outre, l’inscription de type, qui se produit généralement pendant le démarrage de l’application, peut avoir un impact notable sur le temps de démarrage, en fonction du conteneur utilisé. Pour plus d’informations sur l’injection de dépendances dans les applications .NET MAUI, consultez Injection de dépendances.
En guise d’alternative, l’injection de dépendances peut être rendue plus performante en l’implémentant manuellement à l’aide de fabriques.
Créer des applications Shell
Les applications .NET MAUI Shell offrent une expérience de navigation avisée basée sur les menus volants et les onglets. Si l’expérience utilisateur de votre application peut être implémentée avec Shell, il est utile de le faire. Les applications Shell permettent d’éviter une mauvaise expérience de démarrage, car les pages sont créées à la demande en réponse à la navigation plutôt qu’au démarrage de l’application, ce qui se produit avec des applications qui utilisent un TabbedPage. Pour en savoir plus, consultez Présentation de Shell.
Optimiser les performances de ListView
Lorsque vous utilisez ListView, un certain nombre d’expériences utilisateur doit être optimisé :
- Initialisation : intervalle de temps qui commence quand le contrôle est créé et se termine quand les éléments sont affichés à l’écran.
- Défilement : possibilité de faire défiler la liste et de vérifier que l’interface utilisateur est synchrone avec les entrées tactiles.
- Interaction pour l’ajout, la suppression et la sélection d’éléments.
Le contrôle ListView nécessite une application pour fournir des données et des modèles de cellule. Cette opération aura un impact important sur les performances du contrôle. Pour plus d’informations, consultez Données de cache.
Utiliser la programmation asynchrone
La réactivité globale de votre application peut être améliorée et les goulots d’étranglement des performances sont souvent évités à l’aide de la programmation asynchrone. Dans .NET, le Modèle asynchrone basé sur les tâches (TAP) est le modèle de conception recommandé pour les opérations asynchrones. Toutefois, une utilisation incorrecte du TAP peut entraîner des applications non performantes.
Notions de base
Les instructions générales suivantes doivent être suivies lors de l’utilisation du TAP :
- Comprendre le cycle de vie des tâches, représenté par l’énumération
TaskStatus
. Pour plus d’informations, consultez La signification de TaskStatus et État de la tâche. - Utilisez la méthode
Task.WhenAll
pour attendre de façon asynchrone que plusieurs opérations asynchrones se terminent, plutôt queawait
individuellement une série d’opérations asynchrones. Pour en savoir plus, consultez Task.WhenAll. - Utilisez la méthode
Task.WhenAny
pour attendre de façon asynchrone la fin de l’une des opérations asynchrones. Pour en savoir plus, consultez Task.WhenAny. - Utilisez la méthode
Task.Delay
pour produire un objetTask
qui se termine après l’heure spécifiée. Cela est utile pour les scénarios tels que l’interrogation des données et le retard de gestion des entrées utilisateur pendant un temps prédéterminé. Pour en savoir plus, consultez Task.Delay. - Exécutez des opérations intensives de processeur synchrone sur le pool de threads avec la méthode
Task.Run
. Cette méthode est un raccourci pour la méthodeTaskFactory.StartNew
, avec les arguments les plus optimaux définis. Pour en savoir plus, consultez Task.Run. - Évitez d’essayer de créer des constructeurs asynchrones. Utilisez plutôt des événements de cycle de vie ou une logique d’initialisation distincte pour
await
correctement toute initialisation. Pour plus d’informations, consultez Constructeurs asynchrones sur blog.stephencleary.com. - Utilisez le modèle de tâche différé pour éviter d’attendre la fin des opérations asynchrones pendant le démarrage de l’application. Pour plus d'informations, consultez AsyncLazy.
- Créez un enveloppeur de tâches pour les opérations asynchrones existantes, qui n’utilisent pas le TAP, en créant des objets
TaskCompletionSource<T>
. Ces objets bénéficient des avantages de la programmabilitéTask
et vous permettent de contrôler la durée de vie et l’achèvement duTask
associé. Pour plus d’informations, consultez La nature de TaskCompletionSource. - Retourne un objet
Task
, au lieu de renvoyer un objetTask
attendu, lorsqu’il n’est pas nécessaire de traiter le résultat d’une opération asynchrone. Cela est plus performant en raison d’un basculement de contexte inférieur en cours d’exécution. - Utilisez la bibliothèque de flux de données TPL (Task Parallel Library) dans des scénarios tels que le traitement des données à mesure qu’elles sont disponibles ou lorsque vous avez plusieurs opérations qui doivent communiquer entre elles de manière asynchrone. Pour plus d’informations, consultez Flux de données (bibliothèque parallèle de tâches).
UI
Les instructions suivantes doivent être suivies lors de l’utilisation du TAP avec des contrôles d’interface utilisateur :
Appelez une version asynchrone d’une API, si elle est disponible. Cela permet de débloquer le thread d’interface utilisateur, ce qui permettra d’améliorer l’expérience de l’utilisateur avec l’application.
Mettez à jour les éléments de l’interface utilisateur avec des données provenant d’opérations asynchrones sur le thread d’interface utilisateur, afin d’éviter les exceptions levées. Toutefois, les mises à jour de la propriété
ListView.ItemsSource
sont automatiquement marshalées vers le thread d’interface utilisateur. Pour plus d’informations sur la détermination du code en cours d’exécution sur le thread d’interface utilisateur, consultez Créer un thread sur le thread d’interface utilisateur.Important
Toutes les propriétés de contrôle mises à jour via la liaison de données sont automatiquement marshalées sur le thread d’interface utilisateur.
Gestion des erreurs
Les instructions de gestion des erreurs suivantes doivent être suivies lors de l’utilisation du TAP :
- En savoir plus sur la gestion des exceptions asynchrones. Les exceptions non gérées levées par le code qui s’exécutent de manière asynchrone sont propagées vers le thread appelant, sauf dans certains scénarios. Pour plus d’informations, consultez Gestion des exceptions (bibliothèque parallèle de tâches).
- Évitez de créer des méthodes
async void
et créez plutôt des méthodesasync Task
. Celles-ci facilitent la gestion des erreurs, la composabilité et la testabilité. L’exception à cette directive est des gestionnaires d’événements asynchrones, qui doivent retournervoid
. Pour plus d’informations, consultez Éviter Async Void. - Ne mélangez pas le blocage et le code asynchrone en appelant les méthodes
Task.Wait
,Task.Result
ouGetAwaiter().GetResult
, car elles peuvent entraîner un blocage. Toutefois, si cette directive doit être violée, l’approche recommandée consiste à appeler la méthodeGetAwaiter().GetResult
, car elle conserve les exceptions de tâche. Pour plus d’informations, consultez Async All the Way et Gestion des exceptions de tâche dans .NET 4.5. - Utilisez la méthode dans la mesure du possible pour créer du code
ConfigureAwait
sans contexte. Le code sans contexte offre de meilleures performances pour les applications mobiles et est une technique utile pour éviter l’interblocage lors de l’utilisation d’une base de code partiellement asynchrone. Pour plus d’informations, consultez Configurer le contexte. - Utilisez des tâches de continuation pour des fonctionnalités telles que la gestion des exceptions levées par l’opération asynchrone précédente et l’annulation d’une continuation avant son démarrage ou pendant son exécution. Pour plus d’informations, voir Chaînage des tâches à l’aide de tâches continues.
- Utilisez une implémentation ICommand asynchrone lorsque des opérations asynchrones sont appelées à partir du ICommand. Cela garantit que toutes les exceptions de la logique de commande asynchrone peuvent être gérées. Pour plus d’informations, consultez Programmation asynchrone : Modèles pour les applications MVVM asynchrones : Commandes.
Retarder le coût de création d’objets
L’initialisation tardive peut être utilisée pour retarder la création d’un objet jusqu’à sa première utilisation. Cette technique est principalement utilisée pour améliorer les performances, éviter les calculs et réduire les besoins en mémoire.
Envisagez d’utiliser l’initialisation différée pour les objets coûteux à créer dans les scénarios suivants :
- L’application peut ne pas utiliser l’objet.
- D’autres opérations coûteuses doivent être terminées avant que l’objet ne puisse être créé.
La classe Lazy<T>
est utilisée pour définir un type initialisé paresseux, comme illustré dans l’exemple suivant :
void ProcessData(bool dataRequired = false)
{
Lazy<double> data = new Lazy<double>(() =>
{
return ParallelEnumerable.Range(0, 1000)
.Select(d => Compute(d))
.Aggregate((x, y) => x + y);
});
if (dataRequired)
{
if (data.Value > 90)
{
...
}
}
}
double Compute(double x)
{
...
}
L’initialisation tardive se produit lors du premier accès à la propriété Lazy<T>.Value
. Le type encapsulé est créé et retourné lors du premier accès, puis il est stocké en vue d’une utilisation ultérieure.
Pour plus d’informations sur l’initialisation tardive, consultez Initialisation tardive.
Libérer des ressources IDisposable
L’interface IDisposable
fournit un mécanisme permettant de libérer des ressources. Elle fournit une méthode Dispose
qui doit être implémentée pour libérer explicitement des ressources. IDisposable
n’est pas un destructeur et doit être implémenté seulement dans les circonstances suivantes :
- Lorsque la classe contient des ressources non managées. Les ressources non managées qui nécessitent le plus souvent d’être libérées sont les fichiers, les flux et les connexions réseau.
- Lorsque la classe contient des ressources
IDisposable
managées
Les consommateurs de type peuvent ensuite appeler l’implémentation IDisposable.Dispose
pour libérer des ressources lorsque l’instance n’est plus nécessaire. Il existe, pour cela, deux méthodes :
- L’encapsulation de l’objet
IDisposable
dans une instructionusing
- L’encapsulation de l’appel à
IDisposable.Dispose
dans un bloctry
/finally
Encapsuler l’objet IDisposable dans une instruction d’utilisation
L'exemple suivant montre comment envelopper un objet IDisposable
dans une instruction using
:
public void ReadText(string filename)
{
string text;
using (StreamReader reader = new StreamReader(filename))
{
text = reader.ReadToEnd();
}
...
}
La classe StreamReader
implémente IDisposable
, et l’instruction using
fournit une syntaxe qui appelle la méthode StreamReader.Dispose
sur l’objet StreamReader
avant qu’il ne devienne hors de portée. Dans le bloc using
, l’objet StreamReader
est en lecture seule et ne peut pas être réassigné. L’instruction using
garantit également que la méthode Dispose
sera appelée même si une exception se produit, car le compilateur implémente le langage intermédiaire pour un bloc try
/finally
.
Encapsuler l’appel à IDisposable.Dispose dans un bloc essayer/finalement
L'exemple suivant montre comment envelopper l’appel à IDisposable.Dispose
dans un bloc try
/finally
:
public void ReadText(string filename)
{
string text;
StreamReader reader = null;
try
{
reader = new StreamReader(filename);
text = reader.ReadToEnd();
}
finally
{
if (reader != null)
reader.Dispose();
}
...
}
La classe StreamReader
implémente IDisposable
, et le bloc finally
appelle la méthode StreamReader.Dispose
pour libérer la ressource. Pour plus d’informations, consultez Interface IDisposable.
Désinscription des événements
Pour éviter les fuites de mémoire, vous devez vous désabonner des événements avant que l’objet d’abonné ne soit supprimé. Tant que vous ne vous êtes pas désabonné de l’événement, le délégué de cet événement situé dans l’objet de publication comporte une référence au délégué qui encapsule le gestionnaire d’événements de l’abonné. Tant que l’objet de publication contient cette référence, le nettoyage de la mémoire ne va pas récupérer la mémoire de l’objet d’abonné.
L'exemple suivant montre comment se désabonner d’un événement :
public class Publisher
{
public event EventHandler MyEvent;
public void OnMyEventFires()
{
if (MyEvent != null)
MyEvent(this, EventArgs.Empty);
}
}
public class Subscriber : IDisposable
{
readonly Publisher _publisher;
public Subscriber(Publisher publish)
{
_publisher = publish;
_publisher.MyEvent += OnMyEventFires;
}
void OnMyEventFires(object sender, EventArgs e)
{
Debug.WriteLine("The publisher notified the subscriber of an event");
}
public void Dispose()
{
_publisher.MyEvent -= OnMyEventFires;
}
}
La classe Subscriber
se désabonne de l’événement dans sa méthode Dispose
.
Les cycles de référence peuvent également se produire si vous utilisez des gestionnaires d’événements et la syntaxe lambda, car les expressions lambda peuvent référencer et maintenir des objets actifs. Par conséquent, une référence à la méthode anonyme peut être stockée dans un champ et utilisée pour se désabonner de l’événement, comme illustré dans l’exemple suivant :
public class Subscriber : IDisposable
{
readonly Publisher _publisher;
EventHandler _handler;
public Subscriber(Publisher publish)
{
_publisher = publish;
_handler = (sender, e) =>
{
Debug.WriteLine("The publisher notified the subscriber of an event");
};
_publisher.MyEvent += _handler;
}
public void Dispose()
{
_publisher.MyEvent -= _handler;
}
}
Le champ _handler
conserve la référence à la méthode anonyme, et est utilisé pour l’abonnement et le désabonnement aux événements.
Éviter les références circulaires fortes sur iOS et Mac Catalyst
Dans certaines situations, vous pouvez créer des cycles de références fortes qui peuvent empêcher le récupérateur de mémoire de récupérer la mémoire des objets. Par exemple, considérez le cas où une sous-classe dérivée de NSObject, telle qu’une classe qui hérite de UIView, est ajoutée à un conteneur dérivé de NSObject
et est fortement référencée à partir de Objective-C, comme illustré dans l’exemple suivant :
class Container : UIView
{
public void Poke()
{
// Call this method to poke this object
}
}
class MyView : UIView
{
Container _parent;
public MyView(Container parent)
{
_parent = parent;
}
void PokeParent()
{
_parent.Poke();
}
}
var container = new Container();
container.AddSubview(new MyView(container));
Lorsque ce code crée l’instance Container
, l’objet C# aura une référence forte à un objet Objective-C. De même, l’instance MyView
aura également une référence forte à un objet Objective-C.
De plus, l’appel à container.AddSubview
augmente le nombre de références sur l’instance non managée de MyView
. Lorsque cela se produit, le runtime .NET pour iOS crée une instance GCHandle
pour conserver l’objet dans le code MyView
managé actif, car il n’existe aucune garantie que les objets managés conservent une référence à celui-ci. Du point de vue du code managé, l’objet MyView
serait récupéré après l’appel de AddSubview(UIView), s’il n’y avait pas GCHandle
.
L’objet MyView
non managé a un GCHandle
qui pointe vers l’objet managé. Cela s’appelle un lien fort. L’objet managé contient une référence à l’instance de Container
. À son tour, l’instance de Container
a une référence managée à l’objet MyView
.
Dans les cas où un objet contenu conserve un lien vers son conteneur, plusieurs options sont disponibles pour traiter la référence circulaire :
- Évitez la référence circulaire en conservant une référence faible au conteneur.
- Appelez
Dispose
sur les objets. - Rompez manuellement le cycle en affectant au lien vers le conteneur la valeur
null
. - Supprimez manuellement l’objet contenu du conteneur.
Utilisez des références faibles
Pour empêcher un cycle, vous pouvez, entre autres, utiliser une référence faible de l’enfant au parent. Par exemple, le code ci-dessus peut être illustré dans l’exemple suivant :
class Container : UIView
{
public void Poke()
{
// Call this method to poke this object
}
}
class MyView : UIView
{
WeakReference<Container> _weakParent;
public MyView(Container parent)
{
_weakParent = new WeakReference<Container>(parent);
}
void PokeParent()
{
if (weakParent.TryGetTarget (out var parent))
parent.Poke();
}
}
var container = new Container();
container.AddSubview(new MyView container));
Ici, l’objet contenu ne maintient pas le parent actif. Toutefois, le parent maintient l’enfant vivant par le biais de l’appel à container.AddSubView
.
Cela se produit également dans les API iOS qui utilisent le modèle de délégué ou de source de données, où une classe homologue contient l’implémentation. Par exemple, lors de la définition de la propriété Delegate ou de DataSource dans la classe UITableView.
Dans le cas de classes créées uniquement pour l’implémentation d’un protocole, par exemple IUITableViewDataSource, au lieu de créer une sous-classe, vous pouvez implémenter simplement l’interface dans la classe et remplacer la méthode, puis affecter à la propriété DataSource
la valeur this
.
Supprimer des objets avec des références fortes
Si une référence forte existe et qu’il est difficile de supprimer la dépendance, utilisez une méthode Dispose
pour effacer le pointeur parent.
Pour les conteneurs, remplacez la méthode Dispose
pour supprimer les objets contenus, comme illustré dans l’exemple suivant :
class MyContainer : UIView
{
public override void Dispose()
{
// Brute force, remove everything
foreach (var view in Subviews)
{
view.RemoveFromSuperview();
}
base.Dispose();
}
}
Pour un objet enfant qui conserve une référence forte à son parent, effacez la référence au parent dans l’implémentation de Dispose
:
class MyChild : UIView
{
MyContainer _container;
public MyChild(MyContainer container)
{
_container = container;
}
public override void Dispose()
{
_container = null;
}
}