// ========================================================================
//
// Copyright (C) 2016-2017 MARC CHEVALDONNE
//                         marc.chevaldonne.free.fr
//
// Module        : MonLit.cs
// Author        : Marc Chevaldonné
// Creation date : 2016-09-30
//
// ========================================================================

using System;
using System.Collections.Generic;

namespace ex_023_016_ReadOnlyCollection
{
    /// <summary>
    /// L'exemple suivant montre comment encapsuler une collection d'un type dans les deux cas principaux suivants :
    /// 1) le type est immuable (en façade au moins), c'est-à-dire qu'on ne peut pas modifier son état interne en appelant ses méthodes ou propriétés
    /// 2) le type est mutable, ie que l'utilisateur peut modifier l'état interne de ses instances, et notre API l'autorise.
    /// Comment encapsuler correctement une collection d'un tel type ?
    /// Dans l'exemple suivant, le type Nounours est immuable, nous voulons protéger la collection contre :
    /// - les modifications de la collection (ajout ou suppression d'éléments),
    /// - la modification de la collection elle-même (on ne doit pas pouvoir faire de new)
    /// Nous ne cherchons pas à protéger les éléments de la collection (en particulier si le type n'est pas immuable).
    /// En résumé, nous cherchons à rendre publique en lecture seule, la collection mNounours privée de MonLit.
    /// </summary>
    class MonLit
    {
        //on veut encapsuler cette collection d'un type immuable
        private List<Nounours> mNounours = new List<Nounours>(new Nounours[] {
                                    new Nounours(1, "Beluga", new DateTime(2012, 07, 29), 0),
                                    new Nounours(2, "Singe", new DateTime(2009, 08, 09), 1345),
                                    new Nounours(3, "Girafe", new DateTime(2007, 11, 02), 567)});

        /// <summary>
        /// Tentative 1 (mauvaise) :
        /// Ceci est absolument très très mauvais. La propriété semble en lecture seule car le setter est privé, mais la List est type référence.
        /// Certes, on ne peut pas faire NounoursList = new List<Nounours>(); car le setter est privé, 
        /// mais on peut faire par exemple NounoursList[i] = new Nounours(...);
        /// ou bien, NounoursList.Clear();, NounoursList.RemoveAt(i); NounoursList.Add(...);
        /// ...
        /// Cette solution ne protège que du new.
        /// </summary>
        public List<Nounours> NounoursList
        {
            get
            {
                return mNounours;
            }
            private set
            {
                mNounours = value;
            }
        }

        /// <summary>
        /// Tentative 2 (moins mauvaise, mais mauvaise quand même) :
        /// On a l'impression, en utilisant le type de plus haut niveau, ici IEnumerable, d'empêcher toute action. 
        /// Malheureusement, un utilisateur de notre classe, mais s'il n'est pas mal intentionné, peut tenter :
        /// var temp = (NounoursIEnumerable as List<Nounours>);
        /// temp.Add(...);
        /// temp.Clear();
        /// ...
        /// Il peut donc faire les mêmes dégâts que précédemment, même si le fait d'avoir un IEnumerable<Nounours> devrait l'inciter à ne pas le faire.
        /// </summary>
        public IEnumerable<Nounours> NounoursIEnumerable
        {
            get
            {
                return mNounours;
            }
        }

        /// <summary>
        /// Tentative 3 (quasi-identique à la précédente) :
        /// On a l'impression, en utilisant le type de plus haut niveau, ici IReadOnlyCollection, d'empêcher toute action. 
        /// Malheureusement, un utilisateur de notre classe, mais s'il n'est pas mal intentionné, peut tenter :
        /// var temp = (NounoursIROC as List<Nounours>);
        /// temp.Add(...);
        /// temp.Clear();
        /// ...
        /// Il peut donc faire les mêmes dégâts que précédemment, même si le fait d'avoir un IEnumerable<Nounours> devrait l'inciter à ne pas le faire.
        /// </summary>
        public IReadOnlyCollection<Nounours> NounoursIROC
        {
            get
            {
                return mNounours;
            }
        }

        //parfait pour encapsuler un type immuable 
        /// <summary>
        /// Solution (la bonne !)
        /// Ici, on déclare une collection ReadOnlyCollection : il s'agit d'un wrapper autour de la collection mNounours.
        /// Ce wrapper est initialisé dans le constructeur de MonLit.
        /// Le setter privé de NounoursROC empêche de réinitialiser le wrapper (pas d'appel du new).
        /// ReadOnlyCollection<Nounours> empêche l'appel des méthodes Add, Remove, Clear...
        /// On ne peut pas caster NounoursROC en List<Nounours> car il ne s'agit pas de la même instance
        /// (NounoursROC n'est pas la même référence que mNounours, un de ses membres la référence privée mNounours).
        /// 
        /// Avantage énorme : tous les changements de mNounours sont directement répercutés sur NounoursROC puisqu'il l'enveloppe.
        /// 
        /// Attention : si la référence mNounours change (appel de new ou de =), alors NounoursROC n'enveloppe plus mNounours, mais l'ancienne collection mNounours.
        /// </summary>
        public System.Collections.ObjectModel.ReadOnlyCollection<Nounours> NounoursROC
        {
            get;
            private set;
        }

        public MonLit()
        {
            NounoursROC = new System.Collections.ObjectModel.ReadOnlyCollection<Nounours>(mNounours);
        }

        /// <summary>
        /// Variante de la solution
        /// Cette solution donne les mêmes résultats que la précédente à quelques différences près :
        /// - avantage : pas besoin d'initialiser dans le constructeur ou de réinitialiser si la référence mNounours change (appel de new ou =)
        ///   => celle-ci semble donc mieux que la précédente
        /// - inconvénient : à chaque fois qu'on appelle NounoursROC2, on recrée un nouveau wrapper.
        ///   => la solution précédente semble donc moins coûteuse en appel de méthodes, même s'il est vrai que AsReadOnly ne coûte pas très cher.
        /// Conclusion : choisissez l'une des deux solutions, comme vous voulez !
        /// </summary>
        public System.Collections.ObjectModel.ReadOnlyCollection<Nounours> NounoursROC2 => mNounours.AsReadOnly();

    }
}
