Tag Archives: C#

Afficher facilement une taille de fichier sous forme lisible par un humain

Si vous écrivez une application qui a un rapport avec la gestion de fichiers, vous aurez probablement besoin d’afficher la taille des fichiers. Mais si un fichier a une taille de 123456789 octets, ce n’est évidemment pas la valeur qu’il faudra afficher, car c’est difficile à lire, et l’utilisateur n’a généralement pas besoin de connaitre la taille à l’octet près. Vous allez plutôt afficher quelque chose comme 118 Mo.

Ca ne devrait a priori pas être très compliqué, mais en fait il y a différentes façons d’afficher une taille en octets… Par exemple, plusieurs conventions coexistent pour les unités et préfixes :

  • La convention SI (Système International d’Unités) utilise des multiples décimaux, basés sur des puissances de 10 : 1 kilooctet vaut 1000 octets, 1 mégaoctet vaut 1000 kilooctets, etc. Les préfixes sont ceux du système métrique (k, M, G, etc.).
  • La convention CEI  (Commission Electrotechnique Internationale) utilise des multiples binaires, basés sur des puissances de 2 : 1 kibioctet vaut 1024 octets, 1 mébioctet vaut 1024 kibioctets, etc. Les préfixes sont Ki, Mi, Gi, etc., pour éviter la confusion avec le système métrique.
  • Mais aucune de ces conventions n’est communément utilisée : la convention usuelle est d’utiliser des multiples binaires (1024), mais des préfixes décimaux (K, M, G, etc.).

Selon le contexte, on utilisera l’une ou l’autre de ces conventions. Je n’ai jamais vu la convention SI utilisée où que ce soit ; certaines applications (je l’ai vu dans VirtualBox par exemple) utilisent la convention CEI ; la plupart des applications et systèmes d’exploitation utilisent la convention usuelle. Vous pouvez lire cet article Wikipédia si vous voulez en savoir plus : Préfixe binaire.

OK, alors supposons qu’on a choisi la convention usuelle pour l’instant. Maintenant, il faut décider quelle échelle utiliser : voulez-vous écrire 0,11 Go, 118 Mo, 120564 Ko ou 123456789 o ? Habituellement, on choisit l’échelle de façon à ce que la valeur affichée soit entre 1 et 1024.

Il y a encore quelques autres éléments à prendre en compte :

  • Voulez-vous afficher des valeurs entières, ou inclure quelques chiffres après la virgule ?
  • Y a-t-il une unité minimale à utiliser (par exemple, Windows n’affiche jamais des octets : un fichier d’1 octet est affiché comme 1 Ko) ?
  • Comment la valeur doit-elle être arrondie ?
  • Comment faut-il formater la valeur ?
  • Pour les valeurs inférieures à 1 Ko, voulez vous utiliser le mot “octets”, ou juste le symbole “o” ?

Bon, ça suffit! Où veux-tu en venir?

Comme vous pouvez le voir, afficher une taille en octets sous forme lisible par des humains n’est pas aussi évident qu’on aurait pu s’y attendre… J’ai eu à écrire du code pour le faire dans plusieurs applications, et j’ai fini par en avoir assez de refaire à chaque fois, donc j’ai créé une librairie qui s’efforce de couvrir tous les cas d’utilisation. Je l’ai appelée HumanBytes, pour des raisons qui devraient être évidentes… Elle est également disponible sous forme de package NuGet.

Son utilisation est assez simple. Elle est basée sur la classe ByteSizeFormatter, qui expose des propriétés pour contrôler la façon dont la valeur est formatée :

var formatter = new ByteSizeFormatter
{
    Convention = ByteSizeConvention.Binary,
    DecimalPlaces = 1,
    NumberFormat = "#,##0.###",
    MinUnit = ByteSizeUnit.Kilobyte,
    MaxUnit = ByteSizeUnit.Gigabyte,
    RoundingRule = ByteSizeRounding.Closest,
    UseFullWordForBytes = true,
};

var f = new FileInfo("TheFile.jpg");
Console.WriteLine("The size of '{0}' is {1}", f, formatter.Format(f.Length));

Cependant, dans la plupart des cas, vous voudrez simplement utiliser les paramètres par défaut. Vous pouvez le faire facilement grâce à la méthode d’extension Bytes :

var f = new FileInfo("TheFile.jpg");
Console.WriteLine("The size of '{0}' is {1}", f, f.Length.Bytes());

Cette méthode renvoie une instance de la structure ByteSize, dont la méthode ToString formate la valeur avec le formateur par défaut. Vous pouvez changer les paramètres du formateur par défaut via la propriété statique ByteSizeFormatter.Default.

A propos de la localisation

Toutes les langues n’utilisent pas le même symbole pour “octet”, et bien sûr le mot “octet” lui-même est différent d’une langue à l’autre. Pour l’instant, HumanBytes ne supporte que l’anglais et le français ; si vous voulez ajouter le support d’une autre langue, n’hésitez pas à forker le projet, ajouter votre traduction, et faire une pull request. Il n’y a que 3 termes à traduire, donc ça ne devrait pas prendre trop longtemps Winking smile.

Passage de paramètres par référence à une méthode asynchrone

L’asynchronisme dans C# est une fonctionnalité géniale, et je l’ai beaucoup utilisé depuis son apparition. Mais il y a quelques limitations agaçantes; par exemple, on ne peut pas passer des paramètres par référence (ref ou out) à une méthode asynchrone. Il y a de bonnes raisons pour cela; la plus évidente est que si vous passez par référence une variable locale, elle est stockée sur la pile, or la pile ne va pas rester disponible pendant toute l’exécution de la méthode asynchone (seulement jusqu’au premier await), donc l’emplacement de la variable n’existera plus.

Cependant, cette limitation est assez facile à contourner : il suffit de créer une classe Ref<T> pour encapsuler la valeur, et de passer une instance de cette classe par valeur à la méthode asynchrone:

async void btnFilesStats_Click(object sender, EventArgs e)
{
    var count = new Ref<int>();
    var size = new Ref<ulong>();
    await GetFileStats(tbPath.Text, count, size);
    txtFileStats.Text = string.Format("{0} files ({1} bytes)", count, size);
}

async Task GetFileStats(string path, Ref<int> totalCount, Ref<ulong> totalSize)
{
    var folder = await StorageFolder.GetFolderFromPathAsync(path);
    foreach (var f in await folder.GetFilesAsync())
    {
        totalCount.Value += 1;
        var props = await f.GetBasicPropertiesAsync();
        totalSize.Value += props.Size;
    }
    foreach (var f in await folder.GetFoldersAsync())
    {
        await GetFilesCountAndSize(f, totalCount, totalSize);
    }
}

La class Ref<T> ressemble à ceci:

public class Ref<T>
{
    public Ref() { }
    public Ref(T value) { Value = value; }
    public T Value { get; set; }
    public override string ToString()
    {
        T value = Value;
        return value == null ? "" : value.ToString();
    }
    public static implicit operator T(Ref<T> r) { return r.Value; }
    public static implicit operator Ref<T>(T value) { return new Ref<T>(value); }
}

Comme vous pouvez le voir, il n’y a rien de très compliqué. Cette approche peut également être utilisée pour les blocs itérateurs (yield return), qui n’autorisent pas non plus les paramètres ref ou out. Elle a aussi un avantage par rapport aux paramètres ref et out standards: elle permet de rendre le paramètre optionel, par exemple si on n’est pas intéressé par le résultat (évidemment il faut que la méthode appelée gère ce cas de façon appropriée).

Un moyen facile de tester unitairement la validation des arguments null

Quand on teste unitairement une méthode, une des choses à tester est la validation des arguments : par exemple, vérifier que la méthode lève bien une ArgumentNullException quand un argument null est passé pour un paramètre qui ne doit pas être null. Ecrire ce genre de test est très facile, mais c’est une tâche fastidieuse et répétitive, surtout pour une méthode qui a beaucoup de paramètres. J’ai donc écrit une méthode qui automatise en partie cette tâche : elle essaie de passer null pour chacun des arguments spécifiés, et vérifie que la méthode lève bien une ArgumentNullException. Voici un exemple qui teste une méthode d’extension FullOuterJoin :

[Test]
public void FullOuterJoin_Throws_If_Argument_Null()
{
    var left = Enumerable.Empty<int>();
    var right = Enumerable.Empty<int>();
    TestHelper.AssertThrowsWhenArgumentNull(
        () => left.FullOuterJoin(right, x => x, y => y, (k, x, y) => 0, 0, 0, null),
        "left", "right", "leftKeySelector", "rightKeySelector", "resultSelector");
}

Le premier paramètre est une expression lambda qui représente la façon d’appeler la méthode testée. Dans cette lambda, tous les arguments passés à la méthode doivent être valides. Les paramètres suivants sont les noms des paramètres qui ne doivent pas être null. Pour chacun des noms spécifiés, AssertThrowsWhenArgumentNull va remplacer l’argument correspondant par null dans l’expression lambda, compiler et invoquer l’expression lambda, et vérifier que la méthode lève bien une ArgumentNullException.

Grâce à cette méthode, au lieu d’écrire un test pour chacun des arguments à valider, il suffit d’un seul test.

Voici le code de la méthode TestHelper.AssertThrowsWhenArgumentNull (vous pouvez aussi le trouver sur Gist):

using System;
using System.Linq;
using System.Linq.Expressions;
using NUnit.Framework;

namespace MyLibrary.Tests
{
    static class TestHelper
    {
        public static void AssertThrowsWhenArgumentNull(Expression<TestDelegate> expr, params string[] paramNames)
        {
            var realCall = expr.Body as MethodCallExpression;
            if (realCall == null)
                throw new ArgumentException("Expression body is not a method call", "expr");

            var realArgs = realCall.Arguments;
            var paramIndexes = realCall.Method.GetParameters()
                .Select((p, i) => new { p, i })
                .ToDictionary(x => x.p.Name, x => x.i);
            var paramTypes = realCall.Method.GetParameters()
                .ToDictionary(p => p.Name, p => p.ParameterType);
            
            

            foreach (var paramName in paramNames)
            {
                var args = realArgs.ToArray();
                args[paramIndexes[paramName]] = Expression.Constant(null, paramTypes[paramName]);
                var call = Expression.Call(realCall.Method, args);
                var lambda = Expression.Lambda<TestDelegate>(call);
                var action = lambda.Compile();
                var ex = Assert.Throws<ArgumentNullException>(action, "Expected ArgumentNullException for parameter '{0}', but none was thrown.", paramName);
                Assert.AreEqual(paramName, ex.ParamName);
            }
        }

    }
}

Notez que cette méthode a été écrite pour NUnit, mais vous pouvez facilement l’adapter à d’autres frameworks de test unitaire.

J’ai utilisé cette méthode dans ma librairie Linq.Extras, qui fournit de nombreuses méthodes d’extension supplémentaires pour travailler avec des séquences et collections (elle inclut par exemple la méthode FullOuterJoin mentionnée plus haut).

Afficher des suggestions de résultat dans une SearchBox WinRT : bug concernant l’image

Aujourd’hui je me suis heurté à un bug bizarre qui m’a fait perdre une heure ou deux, donc je me suis dit que ça méritait d’écrire un billet à ce sujet au cas où quelqu’un d’autre rencontrerait le même problème.

Le contrôle SearchBox a été ajouté dans Windows 8.1 pour permettre des scénarios de recherche directement dans une application Windows Store. L’une de ses fonctionnalités est l’affichage de suggestions basées sur la saisie de l’utilisateur. Il y a trois sortes de suggestions :

  • Les suggestions d’historique sont les requêtes précédemment effectuées par l’utilisateur. C’est géré automatiquement, donc vous n’avez aucun code à écrire pour que ça marche.
  • Les suggestions de recherche permettent de proposer des termes de recherche en fonction de ce que l’utilisateur a déjà saisi ; si l’utilisateur en sélectionne une, le texte actuel de la recherche est remplacé par celui de la suggestion, et valider la requête lancera la recherche avec ce texte.
  • Les suggestions de résultat sont des suggestions pour des résultats exacts. L’utilisateur peut directement choisir un de ces résultats sans lancer une recherche complète.

Pour fournir des suggestions, il faut gérer l’évènement SuggestionsRequested de la SearchBox, et ajouter des suggestions à l’aide des méthodes AppendQuerySuggestion et AppendResultSuggestion. Concentrons-nous sur les suggestions de résultat.

La méthode AppendResultSuggestion prend plusieurs paramètres, dont l’un représente l’image à afficher pour la suggestion. Il est obligatoire (passer null lèvera une exception), et il est de type IRandomAccessStreamReference, c’est-à-dire quelque chose qui peut fournir un flux. Je trouve ça un peu étrange, vu qu’il aurait été plus naturel de passer une ImageSource, mais c’est comme ça… J’ai donc cherché une classe qui implémente l’interface IRandomAccessStreamReference, et le premier candidat évident que j’ai trouvé était la classe StorageFile, qui représente un fichier. J’ai donc écrit le code suivant :

private async void SearchBox_SuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args)
{
    var deferral = args.Request.GetDeferral();
    try
    {
        var imageUri = new Uri("ms-appx:///test.png");
        var imageRef = await StorageFile.GetFileFromApplicationUriAsync(imageUri);
        args.Request.SearchSuggestionCollection.AppendQuerySuggestion("test");
        args.Request.SearchSuggestionCollection.AppendSearchSeparator("Foo Bar");
        args.Request.SearchSuggestionCollection.AppendResultSuggestion("foo", "Details", "foo", imageRef, "Result");
        args.Request.SearchSuggestionCollection.AppendResultSuggestion("bar", "Details", "bar", imageRef, "Result");
        args.Request.SearchSuggestionCollection.AppendResultSuggestion("baz", "Details", "baz", imageRef, "Result");
    }
    finally
    {
        deferral.Complete();
    }
}

Ce code s’exécute sans aucune erreur, et les suggestions sont affichées… mais l’image n’apparait pas !

http://i.stack.imgur.com/BiF0g.png

J’ai passé un long moment à tout revérifier, à faire plein de petits changements pour essayer de trouver l’origine du problème, j’ai même fait ma propre implémentation de IRandomAccessStreamReference… en vain.

J’ai finalement posté mon problème sur Stack Overflow, et quelqu’un m’a gentiment fourni la solution, qui était très simple : au lieu d’utiliser StorageFile, il faut utiliser RandomAccessStreamReference (ça semble assez évident une fois qu’on sait que ça existe). Le code devient donc :

private void SearchBox_SuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args)
{
    var imageUri = new Uri("ms-appx:///test.png");
    var imageRef = RandomAccessStreamReference.CreateFromUri(imageUri);
    args.Request.SearchSuggestionCollection.AppendQuerySuggestion("test");
    args.Request.SearchSuggestionCollection.AppendSearchSeparator("Foo Bar");
    args.Request.SearchSuggestionCollection.AppendResultSuggestion("foo", "Details", "foo", imageRef, "Result");
    args.Request.SearchSuggestionCollection.AppendResultSuggestion("bar", "Details", "bar", imageRef, "Result");
    args.Request.SearchSuggestionCollection.AppendResultSuggestion("baz", "Details", "baz", imageRef, "Result");
}

(Notez que la méthode n’est plus asynchrone, il n’y a donc plus besoin d’utiliser l’objet deferral).

Les suggestions sont maintenant affichées comme je le voulais, avec l’image :

http://i.imgur.com/cjmogKp.png

La leçon à tirer de cette histoire est que, bien que le paramètre image soit de type IRandomAccessStreamReference, il ne semble pas accepter autre chose qu’une instance de la classe RandomAccessStreamReference. Si vous passez n’importe quelle autre implémentation de l’interface, cela échoue silencieusement et l’image n’est pas affichée. C’est clairement un bug : si le type déclaré du paramètre dans la signature de la méthode et une interface, la méthode devrait accepter n’importe quelle implémentation de cette interface ; sinon, la signature devrait déclarer le type concret. J’ai signalé le bug sur Connect, avec un peu de chance ce sera corrigé dans une future version.

En espérant que ce soit utile à quelqu’un !

Détecter les changements d’une propriété de dépendance dans WinRT

Aujourd’hui j’aimerais partager une astuce que j’ai utilisée en développant ma première application Windows Store. Je suis complètement nouveau sur cette technologie et c’est mon premier billet à ce sujet, donc j’espère que je ne vais pas trop me ridiculiser…

Il est souvent utile d’être notifié quand la valeur d’une propriété de dépendance change ; beaucoup de contrôles exposent des évènements à cet effet, mais ce n’est pas toujours le cas. Par exemple, récemment j’essayais de détecter les changements de la propriété Content d’un ContentControl. En WPF, j’aurais utilisé la classe DependencyPropertyDescriptor, mais elle n’est pas disponible dans WinRT.

Heureusement, il y a un mécanisme qui existe sur toutes les plateformes XAML, et qui peut résoudre ce problème: le binding. La solution est donc simplement de créer une classe avec un propriété “bidon” qui est liée à la propriété qu’on souhaite observer, et d’appeler un handler quand la valeur de cette propriété bidon change. Pour rendre ça un peu plus propre et masquer l’implémentation réelle, j’ai emballé ça sous forme d’une méthode d’extension qui renvoie un IDisposable:

    public static class DependencyObjectExtensions
    {
        public static IDisposable WatchProperty(this DependencyObject target,
                                                string propertyPath,
                                                DependencyPropertyChangedEventHandler handler)
        {
            return new DependencyPropertyWatcher(target, propertyPath, handler);
        }

        class DependencyPropertyWatcher : DependencyObject, IDisposable
        {
            private DependencyPropertyChangedEventHandler _handler;

            public DependencyPropertyWatcher(DependencyObject target,
                                             string propertyPath,
                                             DependencyPropertyChangedEventHandler handler)
            {
                if (target == null) throw new ArgumentNullException("target");
                if (propertyPath == null) throw new ArgumentNullException("propertyPath");
                if (handler == null) throw new ArgumentNullException("handler");

                _handler = handler;

                var binding = new Binding
                {
                    Source = target,
                    Path = new PropertyPath(propertyPath),
                    Mode = BindingMode.OneWay
                };
                BindingOperations.SetBinding(this, ValueProperty, binding);
            }

            private static readonly DependencyProperty ValueProperty =
                DependencyProperty.Register(
                    "Value",
                    typeof(object),
                    typeof(DependencyPropertyWatcher),
                    new PropertyMetadata(null, ValuePropertyChanged));

            private static void ValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var watcher = d as DependencyPropertyWatcher;
                if (watcher == null)
                    return;

                watcher.OnValueChanged(e);
            }

            private void OnValueChanged(DependencyPropertyChangedEventArgs e)
            {
                var handler = _handler;
                if (handler != null)
                    handler(this, e);
            }

            public void Dispose()
            {
                _handler = null;
                // There is no ClearBinding method, so set a dummy binding instead
                BindingOperations.SetBinding(this, ValueProperty, new Binding());
            }
        }
    }

On peut l’utiliser comme ceci:

// Abonnement
watcher = myControl.WatchProperty("Content", myControl_ContentChanged);

// Désabonnement
watcher.Dispose();

J’espère que vous trouverez cela utile!

Récursion terminale en C#

Quel que soit le langage de programmation utilisé, certains traitements s’implémentent naturellement sous forme d’un algorithme récursif (même si ce n’est pas toujours la solution la plus optimale). Le problème de l’approche récursive, c’est qu’elle consomme potentiellement beaucoup d’espace sur la pile : à partir d’un certain niveau de “profondeur” de la récursion, l’espace alloué pour la pile d’exécution du thread est épuisé, et on obtient une erreur de type “débordement de la pile” (StackOverflowException en .NET).

Continue reading Récursion terminale en C#

[C#] Une implémentation du pattern WeakEvent

Comme vous le savez peut-être, la mauvaise utilisation des évènements est l’une des principales causes de fuites mémoires dans une application .NET : en effet, un évènement garde des références aux objets qui y sont abonnés (via le delegate), ce qui empêche le garbage collector de collecter ces objets quand ils ne sont plus utilisés. Le problème est particulièrement vrai pour un évènement statique, puisque les références sont conservées pendant toute l’exécution de l’application. Si on crée de nombreux objets qui s’abonnent à un évènement statique et qu’on ne les désabonne pas, ils restent indéfiniment en mémoire, même si on n’en a plus besoin depuis longtemps, ce qui peut finir par saturer la mémoire.

La solution “évidente” au problème est bien sûr de désabonner les objets qui ne sont plus utilisés. Malheureusement, il n’y a pas toujours de moyen simple de savoir à quel moment on peut désabonner un objet. Une autre approche est d’implémenter le pattern WeakEvent, qui permet de ne garder qu’une référence faible vers les objets abonnés à l’évènement, de façon à ne pas empêcher le garbage collector de les collecter. Microsoft inclut dans WPF des éléments pour implémenter le pattern WeakEvent, et explique comment créer ses propres évènements selon ce pattern, à l’aide de la classe WeakEventManager et de l’interface IWeakEventListener. Cependant, cette technique est assez lourde à mettre en œuvre, aussi bien pour exposer un tel évènement (il faut créer une nouvelle classe dédiée) que pour s’abonner à l’évènement (implémentation de IWeakEventListener).

J’ai donc réfléchi à une autre solution, permettant d’implémenter plus facilement le pattern WeakEvent. Ma première idée était d’utiliser une liste de WeakReference pour stocker la liste des delegates abonnés à l’évènement. Malheureusement, lorsqu’on s’abonne à un évènement, on écrit généralement quelque chose comme ça :

myObject.MyEvent += new EventHandler(myObject_MyEvent);

On crée donc un delegate, mais on ne garde aucune référence dessus. Puisque l’évènement ne référence ce delegate que via une WeakReference, rien n’empêche le garbage collector de le collecter… et c’est effectivement ce qui arrive. Au bout d’un temps variable (pas plus de quelques secondes d’après mes observations), le delegate est collecté et n’est donc plus appelé quand l’évènement est déclenché.

Plutôt que de conserver une référence faible vers le delegate lui même, une meilleure solution serait de faire une référence faible sur l’objet qui implémente la méthode (Delegate.Target). J’ai donc créé une classe WeakDelegate<TDelegate> pour gérer cela :

    public class WeakDelegate<TDelegate> : IEquatable<TDelegate>
    {
        private WeakReference _targetReference;
        private MethodInfo _method;

        public WeakDelegate(Delegate realDelegate)
        {
            if (realDelegate.Target != null)
                _targetReference = new WeakReference(realDelegate.Target);
            else
                _targetReference = null;
            _method = realDelegate.Method;
        }

        public TDelegate GetDelegate()
        {
            return (TDelegate)(object)GetDelegateInternal();
        }

        private Delegate GetDelegateInternal()
        {
            if (_targetReference != null)
            {
                return Delegate.CreateDelegate(typeof(TDelegate), _targetReference.Target, _method);
            }
            else
            {
                return Delegate.CreateDelegate(typeof(TDelegate), _method);
            }
        }

        public bool IsAlive
        {
            get { return _targetReference == null || _targetReference.IsAlive; }
        }


        #region IEquatable<TDelegate> Members

        public bool Equals(TDelegate other)
        {
            Delegate d = (Delegate)(object)other;
            return d != null
                && d.Target == _targetReference.Target
                && d.Method.Equals(_method);
        }

        #endregion

        internal void Invoke(params object[] args)
        {
            Delegate handler = (Delegate)(object)GetDelegateInternal();
            handler.DynamicInvoke(args);
        }
    }

Il ne reste plus qu’à gérer une liste de WeakDelegate<TDelegate>, ce que fait la classe WeakEvent<TDelegate> :

    public class WeakEvent<TEventHandler>
    {
        private List<WeakDelegate<TEventHandler>> _handlers;

        public WeakEvent()
        {
            _handlers = new List<WeakDelegate<TEventHandler>>();
        }

        public virtual void AddHandler(TEventHandler handler)
        {
            Delegate d = (Delegate)(object)handler;
            _handlers.Add(new WeakDelegate<TEventHandler>(d));
        }

        public virtual void RemoveHandler(TEventHandler handler)
        {
            // also remove "dead" (garbage collected) handlers
            _handlers.RemoveAll(wd => !wd.IsAlive || wd.Equals(handler));
        }

        public virtual void Raise(object sender, EventArgs e)
        {
            var handlers = _handlers.ToArray();
            foreach (var weakDelegate in handlers)
            {
                if (weakDelegate.IsAlive)
                {
                    weakDelegate.Invoke(sender, e);
                }
                else
                {
                    _handlers.Remove(weakDelegate);
                }
            }
        }

        protected List<WeakDelegate<TEventHandler>> Handlers
        {
            get { return _handlers; }
        }
    }

Cette classe gère automatiquement la suppression des handlers “morts” (collectés), et fournit une méthode Raise pour faciliter le déclenchement de l’évènement. Elle peut s’utiliser de la façon suivante :

        private WeakEvent<EventHandler> _myEvent = new WeakEvent<EventHandler>();
        public event EventHandler MyEvent
        {
            add { _myEvent.AddHandler(value); }
            remove { _myEvent.RemoveHandler(value); }
        }

        protected virtual void OnMyEvent()
        {
            _myEvent.Raise(this, EventArgs.Empty);
        }

C’est un peu plus long à écrire qu’un évènement “classique”, mais ce n’est finalement pas grand chose par rapport aux avantages que ça apporte… D’ailleurs, on peut facilement créer un “code snippet” pour Visual Studio, qui permet de créer un “évènement faible” en un rien de temps, avec seulement 3 informations à renseigner :

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>wevt</Title>
      <Shortcut>wevt</Shortcut>
      <Description>Code snippet for a weak event</Description>
      <Author>Thomas Levesque</Author>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>type</ID>
          <ToolTip>Event type</ToolTip>
          <Default>EventHandler</Default>
        </Literal>
        <Literal>
          <ID>event</ID>
          <ToolTip>Event name</ToolTip>
          <Default>MyEvent</Default>
        </Literal>
        <Literal>
          <ID>field</ID>
          <ToolTip>Name of the field holding the registered handlers</ToolTip>
          <Default>_myEvent</Default>
        </Literal>
      </Declarations>
      <Code Language="csharp">
        <![CDATA[private WeakEvent<$type$> $field$ = new WeakEvent<EventHandler>();
        public event $type$ $event$
        {
            add { $field$.AddHandler(value); }
            remove { $field$.RemoveHandler(value); }
        }

        protected virtual void On$event$()
        {
            $field$.Raise(this, EventArgs.Empty);
        }
	$end$]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

Ce qui donne dans Visual Studio le résultat suivant :

Code snippet pour implémenter un WeakEvent

Automatiser la vérification des null avec les expressions Linq

Le problème

Je suis sûr qu’il vous est déjà arrivé d’écrire ce genre de code :

X x = GetX();
string name = "Default";
if (xx != null && xx.Foo != null && xx.Foo.Bar != null && xx.Foo.Bar.Baz != null)
{
    name = xx.Foo.Bar.Baz.Name;
}

On veut juste obtenir name = xx.Foo.Bar.Baz.Name, mais on est obligé de tester chaque objet intermédiaire pour vérifier qu’il n’est pas nul, ce qui peut vite s’avérer pénible si la propriété voulue est profondément enfouie dans un graphe d’objets…

Une solution

Linq offre une fonctionnalité qui permet (entre autres) de régler ce problème : les expressions. Il est possible, à partir d’une expression lambda, d’obtenir son arbre syntaxique (ou AST : Abstract Syntax Tree), et de faire toutes sortes de manipulations sur cet arbre. On peut également générer dynamiquement un arbre syntaxique, et le compiler pour obtenir un delegate qu’on pourra ensuite exécuter.

Mais quel rapport avec le problème qui nous intéresse ? Eh bien tout simplement, nous allons pouvoir utiliser les expressions Linq pour analyser l’arbre syntaxique correspondant à l’accès à la propriété xx.Foo.Bar.Baz.Name, et réécrire cet arbre de façon à y ajouter des tests de nullité pour chaque objet intermédiaire.

Nous allons donc créer une méthode d’extension NullSafeEval, qui prendra en premier paramètre une expression lambda définissant comment accéder à la propriété voulue, et en deuxième paramètre la valeur par défaut à renvoyer si un objet nul est rencontré en cours de route.

Cette méthode va transformer l’expression xx.Foo.Bar.Baz.Name en ceci :

    (xx == null)
    ? defaultValue
    : (xx.Foo == null)
      ? defaultValue
      : (xx.Foo.Bar == null)
        ? defaultValue
        : (xx.Foo.Bar.Baz == null)
          ? defaultValue
          : xx.Foo.Bar.Baz.Name;

Voici l’implémentation de la méthode NullSafeEval :

        public static TResult NullSafeEval<TSource, TResult>(this TSource source, Expression<Func<TSource, TResult>> expression, TResult defaultValue)
        {
            var safeExp = Expression.Lambda<Func<TSource, TResult>>(
                NullSafeEvalWrapper(expression.Body, Expression.Constant(defaultValue)),
                expression.Parameters[0]);

            var safeDelegate = safeExp.Compile();
            return safeDelegate(source);
        }

        private static Expression NullSafeEvalWrapper(Expression expr, Expression defaultValue)
        {
            Expression obj;
            Expression safe = expr;

            while (!IsNullSafe(expr, out obj))
            {
                var isNull = Expression.Equal(obj, Expression.Constant(null));

                safe =
                    Expression.Condition
                    (
                        isNull,
                        defaultValue,
                        safe
                    );

                expr = obj;
            }
            return safe;
        }

        private static bool IsNullSafe(Expression expr, out Expression nullableObject)
        {
            nullableObject = null;

            if (expr is MemberExpression || expr is MethodCallExpression)
            {
                Expression obj;
                MemberExpression memberExpr = expr as MemberExpression;
                MethodCallExpression callExpr = expr as MethodCallExpression;

                if (memberExpr != null)
                {
                    // Static fields don't require an instance
                    FieldInfo field = memberExpr.Member as FieldInfo;
                    if (field != null && field.IsStatic)
                        return true;

                    // Static properties don't require an instance
                    PropertyInfo property = memberExpr.Member as PropertyInfo;
                    if (property != null)
                    {
                        MethodInfo getter = property.GetGetMethod();
                        if (getter != null && getter.IsStatic)
                            return true;
                    }
                    obj = memberExpr.Expression;
                }
                else
                {
                    // Static methods don't require an instance
                    if (callExpr.Method.IsStatic)
                        return true;

                    obj = callExpr.Object;
                }

                // Value types can't be null
                if (obj.Type.IsValueType)
                    return true;

                // Instance member access or instance method call is not safe
                nullableObject = obj;
                return false;
            }
            return true;
        }

En résumé, ce code remonte l’arbre de l’expression lambda, en encadrant chaque appel à une propriété ou méthode d’instance par une expression conditionnelle (condition ? valeur si vrai : valeur si faux).

Et voilà comment on utilise cette méthode :

string name = xx.NullSafeEval(x => x.Foo.Bar.Baz.Name, "Default");

C’est tout de même plus clair et plus concis que notre code initial 🙂

Notez que l’implémentation proposée gère non seulement les accès aux propriétés, mais également les appels de méthode, on pourrait donc avoir quelque chose comme ça :

string name = xx.NullSafeEval(x => x.Foo.GetBar(42).Baz.Name, "Default");

Les indexeurs ne sont pas encore gérés, mais pourraient être ajoutés sans grande difficulté ; je vous laisse le soin de le faire si vous en avez l’usage 😉

Limitations

Même si cette solution peut sembler très intéressante au premier abord, lisez la suite avant de vous précipiter pour intégrer ce code dans des programmes réels…

  • Tout d’abord, le code proposé est avant tout un “proof of concept” et n’a pas subi de tests approfondis, sa fiabilité peut donc laisser à désirer.
  • Ensuite, il ne faut pas perdre de vue que la génération dynamique de code à partir d’une expression est très pénalisante pour les performances…

    Une piste possible pour limiter ce problème serait de mettre en cache les delegates obtenus pour chaque expression, de façon à ne pas les regénérer inutilement. Malheureusement, il n’y a pas (à ma connaissance) de moyen simple de comparer deux expressions Linq, ce qui complique sensiblement l’implémentation de ce cache…

  • D’autre part, vous aurez peut-être remarqué que les propriétés et méthodes intermédiaires de l’expression sont évaluées plusieurs fois ; non seulement cela peut avoir un impact non négligeable sur les performances, mais surtout, cela peut causer des effets de bord aux conséquences difficilement prévisibles…

    Une solution possible serait de réécrire l’expression de la façon suivante :

    Foo foo = null;
    Bar bar = null;
    Baz baz = null;
    var name =
        (x == null)
        ? defaultValue
        : ((foo = x.Foo) == null)
          ? defaultValue
          : ((bar = foo.Bar) == null)
            ? defaultValue
            : ((baz = bar.Baz) == null)
              ? defaultValue
              : baz.Name;
    

    Malheureusement, ce n’est pas possible en .NET 3.5 : en effet, cette version ne supporte que des expressions simples, il n’est donc pas possible de déclarer des variables ou de leur affecter une valeur, ni d’écrire plusieurs instructions distinctes. En revanche, en .NET 4.0, le support des expressions Linq a été grandement amélioré, et il est possible de générer ce type de code. J’ai commencé à transformer le code de NullSafeEval pour arriver à ce résultat, mais ça s’avère beaucoup plus complexe que prévu… je publierai la nouvelle méthode si j’arrive à quelque chose de concluant 😉

Au final, je ne recommande donc pas d’utiliser cette technique dans du code réel, en tous cas en l’état actuel. Cela donne cependant un aperçu intéressant des possibilités des expressions Linq. Sachez qu’elle sont également utilisées, entre autres :

  • Pour la génération de requêtes SQL dans des ORM comme Linq to SQL ou Entity Framework
  • Pour construire dynamiquement des prédicats complexes, comme dans la classe PredicateBuilder de Joseph Albahari
  • Pour implementer la “réflexion statique” dont on a beaucoup parlé sur les blogs depuis quelques temps

[C#] Relation parent/enfant et sérialisation XML

Me revoilà avec un peu de retard, j’ai un peu manqué de temps libre ces dernières semaines… Voilà donc un petit post pour présenter une idée qui m’est venue récemment. Pour une fois, il ne sera pas question de WPF, c’est de conception C# qu’il s’agit !

Le problème

Il est assez courant, dans un programme, d’avoir un objet parent qui possède une collection d’enfants ayant une référence vers leur parent. C’est le cas, par exemple, des contrôles Windows Forms, qui possèdent une collection de contrôles enfants (Controls), et une propriété qui indique le contrôle parent (Parent).

Ce type de structure est assez simple à réaliser, cela nécessite juste un peu de code pour maintenir la cohérence de la relation. Là où ça se complique un peu, c’est quand on veut sérialiser l’objet parent en XML… Prenons un exemple simple (purement théorique bien sûr) :

    public class Parent
    {
        public Parent()
        {
            this.Children = new List<Child>();
        }

        public string Name { get; set; }

        public List<Child> Children { get; set; }

        public void AddChild(Child child)
        {
            child.ParentObject = this;
            this.Children.Add(child);
        }

        public void RemoveChild(Child child)
        {
            this.Children.Remove(child);
            child.ParentObject = null;
        }
    }
    public class Child
    {
        public string Name { get; set; }

        public Parent ParentObject { get; set; }
    }

Créons une instance de Parent avec quelques enfants, et essayons de la sérialiser en XML :

            Parent p = new Parent { Name = "The parent" };
            p.AddChild(new Child { Name = "First child" });
            p.AddChild(new Child { Name = "Second child" });

            string xml;
            XmlSerializer xs = new XmlSerializer(typeof(Parent));
            using (StringWriter wr = new StringWriter())
            {
                xs.Serialize(wr, p);
                xml = wr.ToString();
            }

            Console.WriteLine(xml);

Quand on sérialise l’objet Parent, il se produit une InvalidOperationException due à une référence circulaire : en effet, le parent référence les enfants, qui référencent le parent, qui référence les enfants… et ainsi de suite. La première solution qui vient à l’esprit est de ne pas sérialiser la propriété Child.ParentObject : il suffit pour cela de lui appliquer l’attribut XmlIgnore. La sérialisation se passe alors sans problème, mais quand on désérialise, mauvaise surprise : la propriété ParentObject n’est pas renseignée, et pour cause, puisqu’elle n’a pas été sauvegardée…

On pourrait opter pour une solution simple : après la désérialisation : parcourir la liste des enfants pour affecter manuellement la propriété ParentObject. Mais ce n’est vraiment pas très élégant… et comme j’aime bien écrire du beau code, j’ai donc cherché autre chose 😉

La solution

La solution qui m’est venue à l’esprit consiste en une collection générique spécialisée ChildItemCollection<P,T>, et une interface IChildItem<P> que les enfants doivent implémenter.

L’interface IChildItem<P> définit simplement une propriété Parent, de type P :

    /// <summary>
    /// Defines the contract for an object that has a parent object
    /// </summary>
    /// <typeparam name="P">Type of the parent object</typeparam>
    public interface IChildItem<P> where P : class
    {
        P Parent { get; set; }
    }

La collection ChildItemCollection<P,T> implémente IList<T> en délégant l’implémentation à une List<T> ou à la collection passée en paramètre du constructeur, et gère le maintien de la relation parent/enfant :

    /// <summary>
    /// Collection of child items. This collection automatically set the
    /// Parent property of the child items when they are added or removed
    /// </summary>
    /// <typeparam name="P">Type of the parent object</typeparam>
    /// <typeparam name="T">Type of the child items</typeparam>
    public class ChildItemCollection<P, T> : IList<T>
        where P : class
        where T : IChildItem<P>
    {
        private P _parent;
        private IList<T> _collection;

        public ChildItemCollection(P parent)
        {
            this._parent = parent;
            this._collection = new List<T>();
        }

        public ChildItemCollection(P parent, IList<T> collection)
        {
            this._parent = parent;
            this._collection = collection;
        }

        #region IList<T> Members

        public int IndexOf(T item)
        {
            return _collection.IndexOf(item);
        }

        public void Insert(int index, T item)
        {
            if (item != null)
                item.Parent = _parent;
            _collection.Insert(index, item);
        }

        public void RemoveAt(int index)
        {
            T oldItem = _collection[index];
            _collection.RemoveAt(index);
            if (oldItem != null)
                oldItem.Parent = null;
        }

        public T this[int index]
        {
            get
            {
                return _collection[index];
            }
            set
            {
                T oldItem = _collection[index];
                if (value != null)
                    value.Parent = _parent;
                _collection[index] = value;
                if (oldItem != null)
                    oldItem.Parent = null;
            }
        }

        #endregion

        #region ICollection<T> Members

        public void Add(T item)
        {
            if (item != null)
                item.Parent = _parent;
            _collection.Add(item);
        }

        public void Clear()
        {
            foreach (T item in _collection)
            {
                if (item != null)
                    item.Parent = null;
            }
            _collection.Clear();
        }

        public bool Contains(T item)
        {
            return _collection.Contains(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            _collection.CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get { return _collection.Count; }
        }

        public bool IsReadOnly
        {
            get { return _collection.IsReadOnly; }
        }

        public bool Remove(T item)
        {
            bool b = _collection.Remove(item);
            if (item != null)
                item.Parent = null;
            return b;
        }

        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return _collection.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return (_collection as System.Collections.IEnumerable).GetEnumerator();
        }

        #endregion
    }

Voyons donc comment utiliser cette solution dans notre exemple précédent… Nous allons d’abord modifier la classe Child pour qu’elle implémente l’interface IChildItem<Parent> :

    public class Child : IChildItem<Parent>
    {
        public string Name { get; set; }

        [XmlIgnore]
        public Parent ParentObject { get; internal set; }

        #region IChildItem<Parent> Members

        Parent IChildItem<Parent>.Parent
        {
            get
            {
                return this.ParentObject;
            }
            set
            {
                this.ParentObject = value;
            }
        }

        #endregion
    }

Vous remarquerez qu’on implémente l’interface IChildItem<Parent> de façon explicite : l’intérêt est de “masquer” la propriété Parent, qui ne sera accessible que si on manipule l’objet Child via une variable de type IChildItem<Parent>. On définit aussi l’accesseur set de la propriété ParentObject comme étant internal, de façon à empêcher sa modification à partir d’un autre assembly.

Dans la classe Parent, il suffit de remplacer la List<Child> par une ChildItemCollection<Parent, Child>. On supprime au passage les méthodes AddChild et RemoveChild, qui ne sont plus nécessaires puisque la classe ChildItemCollection<P,T> se charge de renseigner la propriété Parent.

    public class Parent
    {
        public Parent()
        {
            this.Children = new ChildItemCollection<Parent, Child>(this);
        }

        public string Name { get; set; }

        public ChildItemCollection<Parent, Child> Children { get; private set; }
    }

Notez qu’on passe au constructeur de ChildItemCollection<Parent, Child> une référence vers l’objet courant : c’est de cette façon que la collection sait quel doit être le parent de ses éléments.

Le code utilisé précédemment pour sérialiser un Parent fonctionne maintenant correctement. Lors de la désérialisation, la propriété Child.ParentObject n’est pas affectée quand le Child est désérialisé (puisqu’elle a l’attribut XmlIgnore), mais lors de l’ajout à la collection Parent.Children.

Cette solution permet donc de conserver la relation parent/enfant lors de la sérialisation XML, sans recourir à des bricolages peu élégants… Notez cependant une restriction : si la propriété ParentObject est modifiée autrement que par la classe ChildItemCollection<P,T>, la cohérence de la relation parent/enfant est rompue. Il est cependant assez simple d’ajouter à l’accesseur set la logique pour maintenir la cohérence, je ne l’ai pas fait dans cet exemple par souci de clarté et de simplicité.