Améliorer les performances des applications
Les performances médiocres de l’application se présentent de nombreuses façons. Il peut rendre une application non réactive, provoquer un défilement lent et réduire la durée de vie de la batterie de l'appareil. Toutefois, l’optimisation des performances implique plus que d’implémenter du code efficace. L’expérience utilisateur des performances des applications doit également être prise en compte. Par exemple, s’assurer que les opérations s’exécutent sans empêcher l’utilisateur d’effectuer d’autres activités peut contribuer à améliorer l’expérience de l’utilisateur.
Il existe de nombreuses techniques pour améliorer les performances et la perception des applications .NET MAUI (Interface Utilisateur Multiplateforme .NET). 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 permettant de déterminer où les optimisations du code auront le plus grand effet 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 Windows, et avec PerfView sur Windows. Pour plus d’informations, consultez Profilage des Applications .NET MAUI.
Les meilleures pratiques suivantes sont recommandées lors du profilage d’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 divers appareils, car la prise de mesures de performances sur un appareil n’affiche pas toujours les caractéristiques de performances d’autres appareils. Toutefois, au minimum, le profilage doit être effectué sur un appareil dont la spécification prévue est la plus faible.
- 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 rapide que l’utilisation d’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 facilement être défini de manière statique. Lier des données qui n'ont pas besoin d'être liées n'apporte aucun avantage, car les liaisons ne sont pas rentables. Par exemple, définir Button.Text = "Accept"
a moins de surcharge que la liaison Button.Text à une propriété viewmodel string
avec la valeur « Accept ».
Choisir la disposition correcte
Une disposition capable d’afficher plusieurs enfants, mais qui n’a qu’un seul enfant, est gaspilleuse. Par exemple, l’exemple suivant montre une VerticalStackLayout avec un seul enfant :
<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 en utilisant des combinaisons d’autres dispositions, car cela entraîne des calculs de disposition inutiles en cours d’exécution. Par exemple, ne tentez pas de reproduire une disposition Grid à l’aide d’une combinaison d’éléments HorizontalStackLayout. L’exemple suivant 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 est inutile, car les calculs de disposition 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’image
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. Il est inutile de décoder une image haute résolution en mémoire lorsqu’elle sera réduite à une taille plus petite pour l’affichage. 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 affichée dans un affichage de liste doit probablement être une résolution inférieure à une image affichée 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 ce faire, créez le flux lors de la création de la page ou lorsque l’événement Page.Appearing se déclenche, puis supprimez le flux lorsque l’événement Page.Disappearing se déclenche.
Lors du téléchargement d’une image à afficher avec la méthode ImageSource.FromUri(Uri), assurez-vous que l’image téléchargée est mise en cache pendant une durée appropriée. Pour plus d'informations, voir mise en cache des images.
Réduire le nombre d’éléments d’une page
La réduction du nombre d’éléments d’une page accélère le rendu de la page. Il existe deux techniques principales pour y parvenir. 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 être visible à l’écran. Si un élément n’est pas visible, car il est masqué derrière d’autres éléments, supprimez l’élément ou définissez sa propriété IsVisible
sur false
. La définition de la propriété IsVisible
sur un élément pour false
conserve l’élément dans l’arborescence visuelle, mais l’exclut du rendu et des calculs de disposition.
La deuxième 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 d’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 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 Applications en stylisant avec XAML.
Réduire 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 Lier une application Android, Lier une application iOSet Lier une application Mac Catalyst.
Réduire la période d’activation de l’application
Toutes les applications disposent d’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 initiale, l’écran de démarrage doit être utilisé pour informer l’utilisateur de la progression pendant la période d’activation, afin de rassurer que l’application n’est pas bloquée. Cette réassurance peut être une barre de progression ou un 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. La période d’activation peut être réduite en veillant à ce que les ressources requises soient empaquetées dans l’application, au lieu d’être récupérées à distance. Par exemple, dans certaines circonstances, il peut être approprié pendant la période d’activation de charger des données d’espace réservé stockées localement. Ensuite, une fois l’interface utilisateur initiale 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. Cela peut être utile pour retarder le chargement d'assemblages supplémentaires, car ces assemblages sont chargés la première fois qu'ils sont utilisés.
Choisir un conteneur d’injection de dépendances soigneusement
Les conteneurs d’injection de dépendances introduisent des contraintes de performances supplémentaires dans les applications mobiles. L'enregistrement et la résolution des types avec un conteneur ont un coût en termes de performance en raison de l'utilisation par le conteneur de la réflexion pour créer chaque type, surtout si les dépendances sont reconstruites à chaque navigation de page dans l'application. S’il existe de nombreuses dépendances ou profondes, le coût de la création peut augmenter considérablement. 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 l’injection de dépendances.
Alternativement, l’injection de dépendances peut être optimisée en l’implémentant manuellement à l’aide d’usines.
Créer des applications Shell
Les applications .NET MAUI Shell offrent une expérience de navigation structurée basée sur les panneaux 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 expérience de démarrage médiocre, 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 plus d’informations, consultez Vue d’ensemble de Shell.
Optimiser les performances de ListView
Lorsque vous utilisez ListView, il existe un certain nombre d’expériences utilisateur qui doivent être optimisées :
- initialisation : l'intervalle de temps qui commence lors de la création du contrôle et se termine lorsque les éléments sont affichés à l'écran.
- défilement : possibilité de faire défiler la liste et de s’assurer que l’interface utilisateur ne retarde pas les mouvements tactiles.
- interaction pour ajouter, supprimer et sélectionner des éléments.
Le contrôle ListView nécessite une application pour fournir des données et des modèles de cellule. La manière dont cela est réalisé aura un impact considérable sur les performances du contrôle. Pour plus d’informations, consultez les données en 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 des 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.
Principes 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 tâche. - Utilisez la méthode
Task.WhenAll
pour attendre de façon asynchrone la fin de plusieurs opérations asynchrones, plutôt que d'await
individuellement une série d’opérations asynchrones. Pour plus d’informations, consultez Task.WhenAll. - Utilisez la méthode
Task.WhenAny
pour attendre asynchrone l'achèvement de l'une des nombreuses opérations asynchrones. Pour plus d’informations, 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 des scénarios tels que l’interrogation de données et le différé du traitement des entrées utilisateur pendant un temps prédéterminé. Pour plus d’informations, 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 plus d’informations, 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 wrapper de tâches pour les opérations asynchrones existantes, qui n’utilisent pas le TAP, en créant des objets
TaskCompletionSource<T>
. Ces objets tirent profit de la programmabilitéTask
et vous permettent de contrôler la durée de vie et l'achèvement de l’élémentTask
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 de moins de basculements de contexte effectués. - 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 dataflow (bibliothèque parallèle de tâches).
interface utilisateur
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 apportées à 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 transférées vers le fil d’exécution de l’interface utilisateur.
Gestion des erreurs
Les instructions de gestion des erreurs suivantes doivent être suivies lors de l’utilisation du TAP :
- Découvrez la gestion asynchrone des exceptions. Les exceptions non gérées levées par le code qui s'exécute 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 concerne les gestionnaires d’événements asynchrones, qui doivent renvoyervoid
. Pour plus d’informations, consultez Avoid Async Void. - Ne mélangez pas de code bloquant et asynchrone en appelant le
Task.Wait
,Task.Result
ou les méthodesGetAwaiter().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
ConfigureAwait
dans la mesure du possible pour créer du code 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 les tâches de continuation pour des fonctionnalités comme gérer les exceptions levées par l’opération asynchrone précédente et annuler une continuation avant son démarrage ou pendant son exécution. Pour plus d’informations, consultez Chaînage de tâches à l'aide de tâches continues.
- Utilisez une implémentation de 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 différée peut être utilisée pour différer la création d’un objet jusqu’à ce qu’il soit utilisé pour la première fois. Cette technique est principalement utilisée pour améliorer les performances, éviter le calcul 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 effectuées avant la création de l’objet.
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 différée se produit la première fois que la propriété Lazy<T>.Value
est appelée. Le type encapsulé est créé et retourné lors du premier accès et stocké pour tout accès ultérieur.
Pour plus d’informations sur l’initialisation différée, consultez initialisation différée.
Libérer des ressources IDisposables
L’interface IDisposable
fournit un mécanisme permettant de libérer des ressources. Il fournit une méthode Dispose
qui doit être implémentée pour libérer explicitement des ressources.
IDisposable
n’est pas un destructeur et ne doit être implémenté que dans les circonstances suivantes :
- Lorsque la classe possède des ressources non managées. Les ressources non managées classiques qui nécessitent la publication incluent des fichiers, des flux et des connexions réseau.
- Lorsque la classe possède des ressources de
IDisposable
managées.
Les consommateurs de types peuvent ensuite appeler l’implémentation IDisposable.Dispose
pour libérer des ressources lorsque l’instance n’est plus nécessaire. Il existe deux approches pour y parvenir :
- En encapsulant l’objet
IDisposable
dans une instructionusing
. - En encapsulant l’appel à
IDisposable.Dispose
dans un bloctry
/finally
.
Encapsuler l’objet IDisposable dans une instruction using
L’exemple suivant montre comment encapsuler 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 pratique qui appelle la méthode StreamReader.Dispose
sur l’objet StreamReader
avant de sortir de portée. Dans le bloc using
, l’objet StreamReader
est en lecture seule et ne peut pas être réaffecté. L’instruction using
garantit également que la méthode Dispose
est appelée même si une exception se produit, car le compilateur implémente le langage intermédiaire (IL) pour un bloc try
/finally
.
Encapsuler l’appel à IDisposable.Dispose dans un bloc try/finally
L’exemple suivant montre comment encapsuler 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.
Se désabonner des événements
Pour éviter les fuites de mémoire, les événements doivent être désinscrits avant la suppression de l’objet abonné. Tant que l’événement n’est pas annulé, le délégué de l’événement dans l’objet de publication a 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, la collecte des ordures ne libère pas la mémoire de l’objet 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;
}
}
Dans sa méthode Dispose
, la classe Subscriber
se désabonne de l’événement.
Les cycles de référence peuvent également se produire lors de l’utilisation de gestionnaires d’événements et de syntaxe lambda, car les expressions lambda peuvent référencer et maintenir les 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 aux événements et se désabonner.
Éviter les références circulaires fortes sur iOS et Mac Catalyst
Dans certaines situations, il est possible de créer des cycles de référence forts qui pourraient empêcher les objets d’avoir leur mémoire récupérée par le garbage collector. 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.
En outre, 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 de GCHandle
pour conserver l’objet MyView
dans le code 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 AddSubview(UIView) si ce n'était pas à cause de GCHandle
.
L’objet MyView
non managé aura un GCHandle
pointant vers l’objet managé, appelé lien fort. L’objet managé contient une référence à l’instance de Container
. À son tour, l’instance Container
aura une référence managée à l’objet MyView
.
Dans les circonstances où un objet contenu conserve un lien vers son conteneur, il existe plusieurs options 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. - Arrêtez manuellement le cycle en définissant le lien vers le conteneur sur
null
. - Supprimez manuellement l’objet contenu du conteneur.
Utiliser des références faibles
Une façon d’empêcher un cycle consiste à 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 maintiendra pas le parent en vie. Toutefois, le parent maintient l’enfant en vie 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 la DataSource dans la classe UITableView.
Dans le cas de classes créées uniquement pour l’implémentation d’un protocole, par exemple la IUITableViewDataSource, ce que vous pouvez faire au lieu de créer une sous-classe, vous pouvez simplement implémenter l’interface dans la classe et remplacer la méthode, et affecter la propriété DataSource
à this
.
Supprimer des objets avec des références fortes
S'il existe une référence forte et qu'il est difficile de supprimer la dépendance, utilisez la 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 Dispose
:
class MyChild : UIView
{
MyContainer _container;
public MyChild(MyContainer container)
{
_container = container;
}
public override void Dispose()
{
_container = null;
}
}