Éviter les dangers des Attached Properties
Les Attached Properties sont une fonctionnalité incontournable de XAML. Surtout avec MVVM.
Malheureusement les Attached Properties peuvent vite devenir la source d’une consommation excessive de mémoire si elles sont mal utilisées.
Pourquoi ?
Dans Attached Properties, il y a surtout le concept d’attacher une propriété. Mais que ce passe-t-il si on ne la détache pas ? Qu’en est-il des ressources utilisées par celle-ci ?
Dans la plupart des cas, tout va bien. Mais si l’on profite de l’ Attached Properties pour s’abonner à des évidemment il faut bien évidemment que l’on se désabonne. Ne pas se désabonner ne pose pas toujours problème, mais si un abonnement permet de garder une référence à un objet qui n’est plus utile, celui-ci restera en mémoire. Le Garbage Collector refusant de s’y attaquer tant qu’il existera des références vers celui-ci.
Pas terrible comme situation…
Comment l’éviter ?
La solution consiste à profiter de l’Attached Properties pour s’abonner à l’event Unload du control. La méthode qui s’exécutera lors de cette Unload se chargera de libérer les ressources inutiles et de se désabonner aux éventuels évènements pour lesquels on a souscrit des abonnements lors de l’association de l’ Attached Property.
Si en plus on se trouve dans un contexte, où cette propriété change souvent, il faut inclure une logique qui se charge de libérer les ressources allouées pour la précédente valeur.
Voici un exemple simple d’abonnement à un Unload. L’exemple n’inclue pas d’allocation de ressources supplémentaires, telles que des control ou des ViewModels supplémentaires, mais la logique et là. L’Unload peut être utilisé pour supprimer ce type de ressources.
public static class TextBox
{
#region Déclarations
private const string EnterCommandPropertyName = "EnterCommand";
public static readonly DependencyProperty EnterCommandProperty;
#endregion
#region Constructeur
/// <summary>
/// Constructeur static
/// </summary>
static TextBox()
{
EnterCommandProperty = DependencyProperty.RegisterAttached(
EnterCommandPropertyName, typeof(ICommand), typeof(TextBox), new PropertyMetadata(null, EnterCommandChanged));
}
#endregion
#region Proriétés
public static ICommand GetEnterCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(EnterCommandProperty);
}
public static void SetEnterCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(EnterCommandProperty, value);
}
#endregion
#region Méthodes
/// <summary>
/// EnterCommand à changé
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void EnterCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Récupération du control
if (!(d is Windows.UI.Xaml.Controls.TextBox control)) return;
control.KeyUp += OnControlKeyUp;
control.Unloaded += OnControlUnloaded;
}
// Récupération de la commandes
private static void OnControlKeyUp(object sender, KeyRoutedEventArgs eventarg)
{
// Test si l'utilisateur a utilisé la touche Enter
if (eventarg.Key != Windows.System.VirtualKey.Enter) return;
// Récupératin et test du control
if (!(sender is Windows.UI.Xaml.Controls.TextBox control)) return;
// Récupération de la commande
ICommand command = GetEnterCommand(control);
// Execution
command?.Execute(null);
}
private static void OnControlUnloaded(object sender, RoutedEventArgs e)
{
// Récupération du control
if (!(sender is Windows.UI.Xaml.Controls.TextBox control)) return;
control.KeyUp -= OnControlKeyUp;
control.Unloaded -= OnControlUnloaded;
}
#endregion
}