Passer un seul élément comme IEnumerable <T>


377

Existe-t-il un moyen courant de passer un seul élément de type Tà une méthode qui attend un IEnumerable<T> paramètre? Le langage est C #, framework version 2.0.

Actuellement, j'utilise une méthode d'assistance (c'est .Net 2.0, j'ai donc tout un tas de méthodes d'aide à la coulée / projection similaires à LINQ), mais cela semble juste idiot:

public static class IEnumerableExt
{
    // usage: IEnumerableExt.FromSingleItem(someObject);
    public static IEnumerable<T> FromSingleItem<T>(T item)
    {
        yield return item; 
    }
}

Une autre façon serait bien sûr de créer et de remplir un List<T>ou un Arrayet de le passer à la place de IEnumerable<T>.

[Modifier] En tant que méthode d'extension, elle pourrait être nommée:

public static class IEnumerableExt
{
    // usage: someObject.SingleItemAsEnumerable();
    public static IEnumerable<T> SingleItemAsEnumerable<T>(this T item)
    {
        yield return item; 
    }
}

Est-ce que j'ai râté quelque chose?

[Edit2] Nous avons trouvé someObject.Yield()(comme @Peter l'a suggéré dans les commentaires ci-dessous) comme étant le meilleur nom pour cette méthode d'extension, principalement pour la brièveté, alors voici avec le commentaire XML si quelqu'un veut l'attraper:

public static class IEnumerableExt
{
    /// <summary>
    /// Wraps this object instance into an IEnumerable&lt;T&gt;
    /// consisting of a single item.
    /// </summary>
    /// <typeparam name="T"> Type of the object. </typeparam>
    /// <param name="item"> The instance that will be wrapped. </param>
    /// <returns> An IEnumerable&lt;T&gt; consisting of a single item. </returns>
    public static IEnumerable<T> Yield<T>(this T item)
    {
        yield return item;
    }
}

6
Je ferais une légère modification dans le corps de la méthode d'extension: if (item == null) yield break; Maintenant, vous êtes empêché de passer null ainsi que de profiter du modèle d'objet null (trivial) pour IEnumerable. ( foreach (var x in xs)gère un vide xstrès bien). Soit dit en passant, cette fonction est l'unité monadique pour la monade de liste qui est IEnumerable<T>, et étant donné le festival d'amour de la monade chez Microsoft, je suis surpris que quelque chose comme ça ne soit pas dans le cadre en premier lieu.
Matt Enright

2
Pour la méthode d'extension, vous ne devez pas la nommer AsEnumerablecar une extension intégrée portant ce nom existe déjà . (Quand Timplémente IEnumerable, par exemple string.)
Jon-Eric

22
Que diriez-vous de nommer la méthode Yield? Rien ne vaut la brièveté.
Philip Guin

4
Suggestions de dénomination ici. "SingleItemAsEnumerable" un peu bavard. "Rendement" décrit l'implémentation plutôt que l'interface - ce qui n'est pas bon. Pour un meilleur nom, je suggère "AsSingleton", qui correspond à la signification exacte du comportement.
Earth Engine

4
Je déteste le left==nullchèque ici. Cela brise la beauté du code et empêche le code d'être plus flexible - et si un jour vous deviez générer un singleton avec quelque chose qui peut être nul? Je veux dire, ce new T[] { null }n'est pas la même chose new T[] {}, et un jour vous devrez peut-être les distinguer.
Earth Engine

Réponses:


100

Votre méthode d'aide est la façon la plus propre de le faire, OMI. Si vous passez une liste ou un tableau, un morceau de code sans scrupules pourrait le transtyper et modifier le contenu, ce qui entraînerait un comportement étrange dans certaines situations. Vous pouvez utiliser une collection en lecture seule, mais cela impliquera probablement encore plus d'habillage. Je pense que votre solution est aussi nette que possible.


bien si la liste / le tableau est construit ad-hoc, sa portée se termine après l'appel de la méthode, donc cela ne devrait pas causer de problèmes
Mario F

S'il est envoyé sous forme de tableau, comment peut-il être modifié? Je suppose qu'il pourrait être converti en un tableau, puis changer la référence en quelque chose d'autre, mais à quoi cela servirait-il? (Mais je manque probablement quelque chose ...)
Svish

2
Supposons que vous ayez décidé de créer un énumérable pour passer à deux méthodes différentes ... puis la première l'a converti en un tableau et modifié le contenu. Vous le passez ensuite en tant qu'argument à une autre méthode, sans être conscient de la modification. Je ne dis pas que c'est probable, juste que le fait d'avoir quelque chose de mutable quand il n'a pas besoin d'être est moins soigné que l'immuabilité naturla.
Jon Skeet

3
C'est une réponse acceptée, et très probablement lue, donc j'ajouterai ma préoccupation ici. J'ai essayé cette méthode, mais cela a interrompu mon appel de compilation précédent vers myDict.Keys.AsEnumerable()myDictétait de type Dictionary<CheckBox, Tuple<int, int>>. Après avoir renommé la méthode d'extension en SingleItemAsEnumerable, tout a commencé à fonctionner. Impossible de convertir IEnumerable <Dictionary <CheckBox, System.Tuple <int, int >>. KeyCollection> 'en' IEnumerable <CheckBox> '. Il existe une conversion explicite (il vous manque un casting?) Je peux vivre avec un hack pendant un certain temps, mais d'autres hackers puissants peuvent-ils trouver un meilleur moyen?
Hamish Grubijan

3
@HamishGrubijan: Il semble que vous vouliez juste dictionary.Valuescommencer. Fondamentalement, ce qui se passe n'est pas clair, mais je vous suggère de commencer une nouvelle question à ce sujet.
Jon Skeet

133

Eh bien, si la méthode attend un, IEnumerablevous devez passer quelque chose qui est une liste, même si elle ne contient qu'un seul élément.

qui passe

new[] { item }

comme l'argument devrait suffire je pense


32
Je pense que vous pourriez même laisser tomber le T, car il sera inféré (à moins que je ne me trompe ici: p)
Svish

2
Je pense que ce n'est pas déduit en C # 2.0.
Groo

4
"Le langage est C #, framework version 2" Le compilateur C # 3 peut déduire T, et le code sera entièrement compatible avec .NET 2.
sisve

la meilleure réponse est au fond?!
Falco Alexander

2
@Svish J'ai suggéré et édité la réponse pour faire exactement cela - nous sommes en 2020 maintenant, donc ce devrait être l'
imho

120

En C # 3.0, vous pouvez utiliser la classe System.Linq.Enumerable:

// using System.Linq

Enumerable.Repeat(item, 1);

Cela va créer un nouvel IEnumerable qui ne contient que votre article.


26
Je pense que cette solution rendra le code plus difficile à lire. :( Répéter sur un seul élément est assez contre-intuitif ne pensez-vous pas?
Drahakar

6
Répéter une fois, ça me va. Solution beaucoup plus simple sans avoir à se soucier d'ajouter une autre méthode d'extension et de s'assurer que l'espace de noms est inclus là où vous voulez l'utiliser.
si618

13
Oui, je new[]{ item }me sers de moi-même, je pensais que c'était une solution intéressante.
luksan

7
Cette solution, c'est de l'argent.
ProVega

À mon humble avis, cela devrait être la réponse acceptée. Il est facile à lire, concis et implémenté sur une seule ligne sur la BCL, sans méthode d'extension personnalisée.
Ryan

35

En C # 3 (je sais que vous avez dit 2), vous pouvez écrire une méthode d'extension générique qui pourrait rendre la syntaxe un peu plus acceptable:

static class IEnumerableExtensions
{
    public static IEnumerable<T> ToEnumerable<T>(this T item)
    {
        yield return item;
    }
}

le code client est alors item.ToEnumerable().


Merci, j'en suis conscient (fonctionne dans .Net 2.0 si j'utilise C # 3.0), je me demandais simplement s'il y avait un mécanisme intégré pour cela.
Groo

12
Ceci est une très belle alternative. Je veux seulement suggérer de renommer pour ToEnumerableéviter de gâcherSystem.Linq.Enumerable.AsEnumerable
Fede

3
En remarque, il existe déjà une ToEnumerableméthode d'extension pour IObservable<T>, donc ce nom de méthode interfère également avec les conventions existantes. Honnêtement, je suggère de ne pas utiliser du tout une méthode d'extension, simplement parce qu'elle est trop générique.
cwharris

14

Cette méthode d'assistance fonctionne pour un élément ou plusieurs.

public static IEnumerable<T> ToEnumerable<T>(params T[] items)
{
    return items;
}    

1
Variation utile, car elle prend en charge plusieurs éléments. J'aime qu'il s'appuie sur paramspour construire le tableau, donc le code résultant est propre. Je n'ai pas décidé si je l'aime plus ou moins que new T[]{ item1, item2, item3 };pour plusieurs articles.
ToolmakerSteve

Intelligent! J'adore
Mitsuru Furuta

10

Je suis un peu surpris que personne n'ait suggéré une nouvelle surcharge de la méthode avec un argument de type T pour simplifier l'API client.

public void DoSomething<T>(IEnumerable<T> list)
{
    // Do Something
}

public void DoSomething<T>(T item)
{
    DoSomething(new T[] { item });
}

Maintenant, votre code client peut simplement faire ceci:

MyItem item = new MyItem();
Obj.DoSomething(item);

ou avec une liste:

List<MyItem> itemList = new List<MyItem>();
Obj.DoSomething(itemList);

19
Encore mieux, vous pourriez avoir DoSomething<T>(params T[] items)ce qui signifie que le compilateur gérerait la conversion d'un seul élément en un tableau. (Cela vous permettrait également de passer plusieurs éléments séparés et, encore une fois, le compilateur se chargerait de les convertir en un tableau pour vous.)
LukeH

7

Soit (comme cela a été dit précédemment)

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

ou

MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(myObject, 1));

En remarque, la dernière version peut aussi être sympa si vous voulez une liste vide d'un objet anonyme, par exemple

var x = MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(new { a = 0, b = "x" }, 0));

Merci, bien que Enumerable.Repeat soit nouveau dans .Net 3.5. Il semble se comporter de manière similaire à la méthode d'assistance ci-dessus.
Groo

7

Comme je viens de le découvrir et de voir que l'utilisateur LukeH l'a également suggéré, une belle façon simple de le faire est la suivante:

public static void PerformAction(params YourType[] items)
{
    // Forward call to IEnumerable overload
    PerformAction(items.AsEnumerable());
}

public static void PerformAction(IEnumerable<YourType> items)
{
    foreach (YourType item in items)
    {
        // Do stuff
    }
}

Ce modèle vous permettra d'appeler la même fonctionnalité de multiples façons: un seul élément; plusieurs éléments (séparés par des virgules); un tableau; une liste; une énumération, etc.

Je ne suis pas sûr à 100% de l'efficacité de l'utilisation de la méthode AsEnumerable, mais cela fonctionne vraiment.

Mise à jour: la fonction AsEnumerable semble assez efficace! ( référence )


En fait, vous n'en avez pas du tout besoin .AsEnumerable(). Array YourType[]implémente déjà IEnumerable<YourType>. Mais ma question faisait référence au cas où seule la deuxième méthode (dans votre exemple) est disponible, et vous utilisez .NET 2.0, et vous souhaitez passer un seul élément.
Groo

2
Oui, mais vous constaterez que vous pourriez avoir un débordement de pile ;-)
teppicymon

Et pour répondre à votre vraie question (pas celle à laquelle je cherchais à répondre moi-même!), Oui, vous devez à peu près le convertir en tableau selon la réponse de Mario
teppicymon

2
Que diriez-vous de simplement définir une IEnumerable<T> MakeEnumerable<T>(params T[] items) {return items;}méthode? On pourrait alors l'utiliser avec tout ce qui attendait un IEnumerable<T>, pour n'importe quel nombre d'articles discrets. Le code doit être essentiellement aussi efficace que la définition d'une classe spéciale pour renvoyer un seul élément.
supercat

6

Bien que ce soit exagéré pour une méthode, je pense que certaines personnes peuvent trouver les extensions interactives utiles.

Les extensions interactives (Ix) de Microsoft incluent la méthode suivante.

public static IEnumerable<TResult> Return<TResult>(TResult value)
{
    yield return value;
}

Qui peut être utilisé comme ceci:

var result = EnumerableEx.Return(0);

Ix ajoute de nouvelles fonctionnalités que l'on ne trouve pas dans les méthodes d'extension Linq d'origine et est le résultat direct de la création des extensions réactives (Rx).

Pensez, Linq Extension Methods+ Ix= Rxpour IEnumerable.

Vous pouvez trouver à la fois Rx et Ix sur CodePlex .


5

Ceci est 30% plus rapide que yieldou Enumerable.Repeatlorsqu'il est utilisé en foreachraison de cette optimisation du compilateur C # , et des mêmes performances dans d'autres cas.

public struct SingleSequence<T> : IEnumerable<T> {
    public struct SingleEnumerator : IEnumerator<T> {
        private readonly SingleSequence<T> _parent;
        private bool _couldMove;
        public SingleEnumerator(ref SingleSequence<T> parent) {
            _parent = parent;
            _couldMove = true;
        }
        public T Current => _parent._value;
        object IEnumerator.Current => Current;
        public void Dispose() { }

        public bool MoveNext() {
            if (!_couldMove) return false;
            _couldMove = false;
            return true;
        }
        public void Reset() {
            _couldMove = true;
        }
    }
    private readonly T _value;
    public SingleSequence(T value) {
        _value = value;
    }
    public IEnumerator<T> GetEnumerator() {
        return new SingleEnumerator(ref this);
    }
    IEnumerator IEnumerable.GetEnumerator() {
        return new SingleEnumerator(ref this);
    }
}

dans ce test:

    // Fastest among seqs, but still 30x times slower than direct sum
    // 49 mops vs 37 mops for yield, or c.30% faster
    [Test]
    public void SingleSequenceStructForEach() {
        var sw = new Stopwatch();
        sw.Start();
        long sum = 0;
        for (var i = 0; i < 100000000; i++) {
            foreach (var single in new SingleSequence<int>(i)) {
                sum += single;
            }
        }
        sw.Stop();
        Console.WriteLine($"Elapsed {sw.ElapsedMilliseconds}");
        Console.WriteLine($"Mops {100000.0 / sw.ElapsedMilliseconds * 1.0}");
    }

Merci, il est logique de créer une structure pour ce cas afin de réduire la charge du GC dans les boucles serrées.
Groo

1
L'énumérateur et l'énumérable vont tous les deux être mis en boîte lorsqu'ils seront retournés ....
Stephen Drew

2
Ne comparez pas en utilisant le chronomètre. Utilisez Benchmark.NET github.com/dotnet/BenchmarkDotNet
NN_

1
Je viens de le mesurer et l' new [] { i }option est environ 3,2 fois plus rapide que votre option. De plus, votre option est environ 1,2 fois plus rapide que l'extension avec yield return.
lorond

Vous pouvez accélérer cela en utilisant une autre optimisation du compilateur C #: ajoutez une méthode SingleEnumerator GetEnumerator()qui renvoie votre structure. Le compilateur C # utilisera cette méthode (il la recherche via la saisie de canard) au lieu de celles de l'interface et évite ainsi la boxe. De nombreuses collections intégrées utilisent cette astuce, comme List . Remarque: cela ne fonctionnera que si vous avez une référence directe à SingleSequence, comme dans votre exemple. Si vous le stockez dans une IEnumerable<>variable, l'astuce ne fonctionnera pas (la méthode d'interface sera utilisée)
enzi

5

IanG a un bon article sur le sujet , suggérant EnumerableFrom()comme nom (et mentionne que Haskell et Rx l'appellent Return). L'IIF de F # l'appelle aussi Return . F # Seqappelle l'opérateursingleton<'T> .

Tentant si vous êtes prêt à être centré sur C # est de l'appeler Yield[faisant allusion à la personne yield returnimpliquée dans sa réalisation].

Si vous êtes intéressé par les aspects de la performance, James Michael Hare a également un article de retour zéro ou un article qui vaut bien une analyse.


4

Ce n'est peut-être pas mieux mais c'est plutôt cool:

Enumerable.Range(0, 1).Select(i => item);

Ce n'est pas. C'est différent.
nawfal

Ewww. C'est beaucoup de détails, juste pour transformer un élément en un énumérable.
ToolmakerSteve

1
Cela équivaut à utiliser une machine Rube Goldberg pour préparer le petit-déjeuner. Oui ça marche, mais ça saute à travers 10 cerceaux pour y arriver. Une chose simple comme celle-ci peut être le goulot d'étranglement des performances lorsqu'il est effectué dans une boucle étroite qui est exécutée des millions de fois. En pratique, l'aspect performance n'a pas d'importance dans 99% des cas, mais personnellement, je pense toujours que c'est une surpuissance inutile.
enzi

4

Je suis d'accord avec les commentaires de @ EarthEngine sur le message d'origine, qui est que "AsSingleton" est un meilleur nom. Voir cette entrée wikipedia . Ensuite, il résulte de la définition de singleton que si une valeur nulle est passée comme argument, 'AsSingleton' devrait retourner un IEnumerable avec une seule valeur null au lieu d'un IEnumerable vide qui réglerait le if (item == null) yield break;débat. Je pense que la meilleure solution est d'avoir deux méthodes: 'AsSingleton' et 'AsSingletonOrEmpty'; où, dans le cas où un NULL est passé en argument, 'AsSingleton' renverra une seule valeur Null et 'AsSingletonOrEmpty' renverra un IEnumerable vide. Comme ça:

public static IEnumerable<T> AsSingletonOrEmpty<T>(this T source)
{
    if (source == null)
    {
        yield break;
    }
    else
    {
        yield return source;
    }
}

public static IEnumerable<T> AsSingleton<T>(this T source)
{
    yield return source;
}

Ensuite, celles-ci seraient plus ou moins analogues aux méthodes d'extension «First» et «FirstOrDefault» sur IEnumerable, ce qui semble juste.


2

La façon la plus simple que je dirais serait new T[]{item};; il n'y a pas de syntaxe pour ce faire. L'équivalent le plus proche auquel je peux penser est le paramsmot - clé, mais bien sûr, cela vous oblige à avoir accès à la définition de la méthode et n'est utilisable qu'avec des tableaux.


2
Enumerable.Range(1,1).Select(_ => {
    //Do some stuff... side effects...
    return item;
});

Le code ci-dessus est utile lors de l'utilisation comme

var existingOrNewObject = MyData.Where(myCondition)
       .Concat(Enumerable.Range(1,1).Select(_ => {
           //Create my object...
           return item;
       })).Take(1).First();

Dans l'extrait de code ci-dessus, il n'y a pas de vérification vide / nulle et il est garanti qu'un seul objet est renvoyé sans avoir peur des exceptions. De plus, étant donné qu'il est paresseux, la fermeture ne sera pas exécutée tant qu'il n'aura pas été prouvé qu'aucune donnée existante ne correspond aux critères.


Cette réponse a été automatiquement étiquetée "faible qualité". Comme expliqué dans l' aide ("La concision est acceptable, mais des explications plus complètes sont meilleures."), Veuillez le modifier pour dire à l'OP ce qu'il fait mal, de quoi parle votre code.
Sandra Rossi

Je vois que la majeure partie de cette réponse, y compris la partie à laquelle je réponds, a été modifiée plus tard par quelqu'un d'autre. mais si l'intention du deuxième extrait est de fournir une valeur par défaut si MyData.Where(myCondition)est vide, qui est déjà possible (et plus simple) avec DefaultIfEmpty(): var existingOrNewObject = MyData.Where(myCondition).DefaultIfEmpty(defaultValue).First();. Cela peut être encore simplifié var existingOrNewObject = MyData.FirstOrDefault(myCondition);si vous le souhaitez default(T)et non une valeur personnalisée.
BACON

0

Je suis un peu en retard à la fête mais je vais quand même partager mon chemin. Mon problème était que je voulais lier ItemSource ou WPF TreeView à un seul objet. La hiérarchie ressemble à ceci:

Projet> Parcelle (s)> Salle (s)

Il n'y aura toujours qu'un seul projet, mais je voulais toujours montrer le projet dans l'arborescence, sans avoir à passer une collection avec seulement un seul objet comme certains l'ont suggéré.
Comme vous ne pouvez transmettre que des objets IEnumerable en tant que ItemSource, j'ai décidé de rendre ma classe IEnumerable:

public class ProjectClass : IEnumerable<ProjectClass>
{
    private readonly SingleItemEnumerator<AufmassProjekt> enumerator;

    ... 

    public IEnumerator<ProjectClass > GetEnumerator() => this.enumerator;

    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}

Et créez mon propre énumérateur en conséquence:

public class SingleItemEnumerator : IEnumerator
{
    private bool hasMovedOnce;

    public SingleItemEnumerator(object current)
    {
        this.Current = current;
    }

    public bool MoveNext()
    {
        if (this.hasMovedOnce) return false;
        this.hasMovedOnce = true;
        return true;
    }

    public void Reset()
    { }

    public object Current { get; }
}

public class SingleItemEnumerator<T> : IEnumerator<T>
{
    private bool hasMovedOnce;

    public SingleItemEnumerator(T current)
    {
        this.Current = current;
    }

    public void Dispose() => (this.Current as IDisposable).Dispose();

    public bool MoveNext()
    {
        if (this.hasMovedOnce) return false;
        this.hasMovedOnce = true;
        return true;
    }

    public void Reset()
    { }

    public T Current { get; }

    object IEnumerator.Current => this.Current;
}

Ce n'est probablement pas la solution "la plus propre" mais cela a fonctionné pour moi.

EDIT
Pour maintenir le principe de responsabilité unique comme l'a souligné @Groo, j'ai créé une nouvelle classe wrapper:

public class SingleItemWrapper : IEnumerable
{
    private readonly SingleItemEnumerator enumerator;

    public SingleItemWrapper(object item)
    {
        this.enumerator = new SingleItemEnumerator(item);
    }

    public object Item => this.enumerator.Current;

    public IEnumerator GetEnumerator() => this.enumerator;
}

public class SingleItemWrapper<T> : IEnumerable<T>
{
    private readonly SingleItemEnumerator<T> enumerator;

    public SingleItemWrapper(T item)
    {
        this.enumerator = new SingleItemEnumerator<T>(item);
    }

    public T Item => this.enumerator.Current;

    public IEnumerator<T> GetEnumerator() => this.enumerator;

    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}

Que j'ai utilisé comme ça

TreeView.ItemSource = new SingleItemWrapper(itemToWrap);

EDIT 2
J'ai corrigé une erreur de MoveNext()méthode.


La SingleItemEnumerator<T>classe a du sens, mais faire d'une classe un "élément unique en IEnumerablesoi" semble être une violation du principe de responsabilité unique. Peut-être que cela le rend plus pratique, mais je préférerais quand même l'envelopper au besoin.
Groo

0

A classer sous "Pas forcément une bonne solution, mais quand même ... une solution" ou "Stupid LINQ tricks", vous pouvez combiner Enumerable.Empty<>()avec Enumerable.Append<>()...

IEnumerable<string> singleElementEnumerable = Enumerable.Empty<string>().Append("Hello, World!");

... ou Enumerable.Prepend<>()...

IEnumerable<string> singleElementEnumerable = Enumerable.Empty<string>().Prepend("Hello, World!");

Les deux dernières méthodes sont disponibles depuis .NET Framework 4.7.1 et .NET Core 1.0.

Il s'agit d'une solution réalisable si l'on avait vraiment l' intention d'utiliser des méthodes existantes au lieu d'écrire les leurs, bien que je ne sois pas sûr que ce soit plus ou moins clair que la Enumerable.Repeat<>()solution . Il s'agit certainement d'un code plus long (en partie parce que l'inférence de paramètres de type n'est pas possible Empty<>()) et crée cependant deux fois plus d'objets énumérateurs.

Pour compléter ce "Saviez-vous que ces méthodes existent?" réponse, Array.Empty<>()pourrait être substitué Enumerable.Empty<>(), mais il est difficile de prétendre que cela rend la situation ... meilleure.


0

J'ai récemment posé la même question sur un autre post ( Existe-t-il un moyen d'appeler une méthode C # nécessitant un IEnumerable <T> avec une seule valeur? ... avec benchmarking ).

Je voulais que les gens s'arrêtent ici pour voir la brève comparaison de référence montrée à ce poste plus récent pour 4 des approches présentées dans ces réponses.

Il semble que le simple fait d'écrire new[] { x }les arguments de la méthode soit la solution la plus courte et la plus rapide.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.