Question

Est-il possible de préciser que les membres d'une classe imbriquée sont accessibles par la classe englobante, mais pas d'autres classes?

Voici une illustration du problème (bien sûr mon code réel est un peu plus complexe ...):

public class Journal
{
    public class JournalEntry
    {
        public JournalEntry(object value)
        {
            this.Timestamp = DateTime.Now;
            this.Value = value;
        }

        public DateTime Timestamp { get; private set; }
        public object Value { get; private set; }
    }

    // ...
}

Je voudrais éviter le code client de créer des instances de JournalEntry, mais Journal dois être en mesure de les créer. Si je fais le public constructeur, tout le monde peut créer des instances ... mais si je fais privée, Journal ne sera pas en mesure de!

Notez que la classe JournalEntry doit être publique, parce que je veux être en mesure d'exposer les entrées existantes au code client.

Toute suggestion serait appréciée!


MISE À JOUR: Merci à tous pour vos commentaires, alors je suis allée à l'interface IJournalEntry publique, mis en œuvre par une classe JournalEntry privée (malgré la dernière exigence de ma question ...)

Était-ce utile?

La solution

Si votre classe est pas trop complexe, vous pouvez soit utiliser une interface qui est publiquement visible et de rendre le constructeur privé réelle classe implémentant ou vous pouvez faire une protection pour la classe JornalEntry et une JornalEntryInstance de classe privée dérivé de JornalEntry avec un public constructeur qui est en fait instancié par votre Journal.

public class Journal
{
    public class JournalEntry
    {
        protected JournalEntry(object value)
        {
            this.Timestamp = DateTime.Now;
            this.Value = value;
        }

        public DateTime Timestamp { get; private set; }
        public object Value { get; private set; }
    }

    private class JournalEntryInstance: JournalEntry
    { 
        public JournalEntryInstance(object value): base(value)
        { }
    }
    JournalEntry CreateEntry(object value)
    {
        return new JournalEntryInstance(value);
    }
}

Si votre classe réelle est trop complexe pour faire soit de cela et vous pouvez vous en sortir avec le constructeur étant pas complètement invisible, vous pouvez faire le constructeur interne il est visible que dans l'ensemble.

Si cela aussi est infaisable, vous pouvez toujours faire le constructeur de réflexion privée et l'utilisation de l'appeler de votre classe journal:

typeof(object).GetConstructor(new Type[] { }).Invoke(new Object[] { value });

Maintenant que je pense, une autre possibilité serait d'utiliser un délégué privé dans la classe contenant qui est définie de la classe interne

public class Journal
{
    private static Func<object, JournalEntry> EntryFactory;
    public class JournalEntry
    {
        internal static void Initialize()
        {
            Journal.EntryFactory = CreateEntry;
        }
        private static JournalEntry CreateEntry(object value)
        {
            return new JournalEntry(value);
        }
        private JournalEntry(object value)
        {
            this.Timestamp = DateTime.Now;
            this.Value = value;
        }

        public DateTime Timestamp { get; private set; }
        public object Value { get; private set; }
    }

    static Journal()
    {
        JournalEntry.Initialize();
    }

    static JournalEntry CreateEntry(object value)
    {
        return EntryFactory(value);
    }
}

Cela devrait vous donner à vos niveaux de visibilité souhaités sans avoir besoin de recourir à la réflexion lente ou l'introduction d'autres classes / interfaces

Autres conseils

En fait, il y a une solution complète et simple à ce problème qui ne nécessite pas de modifier le code client ou la création d'une interface.

Cette solution est plus rapide que la solution à base d'interface pour la plupart des cas, et plus facile à coder.

public class Journal
{
  private static Func<object, JournalEntry> _newJournalEntry;

  public class JournalEntry
  {
    static JournalEntry()
    {
      _newJournalEntry = value => new JournalEntry(value);
    }
    private JournalEntry(object value)
    {
      ...

Faire JournalEntry un type imbriqué privé . Tous les membres du public seront visibles uniquement au type englobante.

public class Journal
{
    private class JournalEntry
    {
    }
}

Si vous avez besoin de faire des objets JournalEntry disponibles à d'autres classes, les exposer via une interface publique:

public interface IJournalEntry
{
}

public class Journal
{
    public IEnumerable<IJournalEntry> Entries
    {
        get { ... }
    }

    private class JournalEntry : IJournalEntry
    {
    }
}

Une approche plus simple est d'utiliser un simple constructeur de internal, mais assurez-vous que l'appelant prouver qui ils sont en fournissant une référence que l'appelant légitime pourrait savoir (on n'a pas besoin d'être préoccupé par réflexion non publique, parce que si l'appelant a accès à la réflexion non publiques alors que nous avons déjà perdu le combat - ils peuvent accéder à un constructeur de private directement); par exemple:

class Outer {
    // don't pass this reference outside of Outer
    private static readonly object token = new object();

    public sealed class Inner {
        // .ctor demands proof of who the caller is
        internal Inner(object token) {
            if (token != Outer.token) {
                throw new InvalidOperationException(
                    "Seriously, don't do that! Or I'll tell!");
            }
            // ...
        } 
    }

    // the outer-class is allowed to create instances...
    private static Inner Create() {
        return new Inner(token);
    }
}

Dans ce cas, vous pouvez soit:

  1. le constructeur interne - cela empêche ceux en dehors de cette Assemblée en créant de nouveaux cas ou ...
  2. Refactoriser la classe JournalEntry d'utiliser une interface publique et privée ou faire interne la classe JournalEntry réelle. L'interface peut alors être exposé des collections alors que la mise en œuvre réelle est cachée.

je l'ai mentionné interne comme modificateur valide ci-dessus mais en fonction de vos besoins, privés peuvent être une alternative mieux adaptée.

Modifier Désolé je l'ai mentionné constructeur privé, mais vous avez déjà eu affaire à ce point dans votre question. Je présente mes excuses pour ne pas lire correctement!

Pour la classe imbriquée générique =)

Je sais que cela est une question ancienne et il a déjà une réponse acceptée, néanmoins pour les nageurs de Google qui peuvent avoir un scénario similaire à la mienne cette réponse peut apporter une aide.

Je suis tombé sur cette question car je devais mettre en œuvre la même fonction que l'OP. Pour mon premier scénario cette et ce réponses ont travaillé très bien. Néanmoins je avais besoin aussi d'exposer une classe générique imbriquée. Le problème est que vous ne pouvez pas exposer un champ de type délégué (le champ d'usine) avec des paramètres génériques ouverts sans faire votre propre classe générique, mais évidemment ce n'est pas ce que nous voulons, donc, voici ma solution pour ce scénario:

public class Foo
{
    private static readonly Dictionary<Type, dynamic> _factories = new Dictionary<Type, dynamic>();

    private static void AddFactory<T>(Func<Boo<T>> factory)
        => _factories[typeof(T)] = factory;

    public void TestMeDude<T>()
    {
        if (!_factories.TryGetValue(typeof(T), out var factory))
        {
            Console.WriteLine("Creating factory");
            RuntimeHelpers.RunClassConstructor(typeof(Boo<T>).TypeHandle);
            factory = _factories[typeof(T)];
        }
        else
        {
            Console.WriteLine("Factory previously created");
        }

        var boo = (Boo<T>)factory();
        boo.ToBeSure();
    }

    public class Boo<T>
    {
        static Boo() => AddFactory(() => new Boo<T>());

        private Boo() { }

        public void ToBeSure() => Console.WriteLine(typeof(T).Name);
    }
}

Nous avons Boo comme notre classe imbriquée interne avec un constructeur privé et nous mantenir sur notre classe parente un dictionnaire avec ces usines génériques qui profitent de dynamique . Donc, chaque fois que TestMeDude est appelé, Foo cherche si l'usine T a déjà été créé, sinon il crée appeler classe imbriquée constructeur statique.

Test:

private static void Main()
{
    var foo = new Foo();

    foo.TestMeDude<string>();
    foo.TestMeDude<int>();
    foo.TestMeDude<Foo>();

    foo.TestMeDude<string>();

    Console.ReadLine();
}

La sortie est la suivante:

La solution Grizzly proposé ne fait un peu dur pour créer la classe imbriquée quelque part ailleurs, mais pas impossible, comme Tim Pohlmann a écrit quelqu'un peut encore hériter et utiliser la classe héritant cteur.

Je profite du fait que la classe imbriquée peut accéder aux propriétés privées de conteneurs, de sorte que le conteneur demande gentiment et la classe imbriquée donne accès à la cteur.

 public class AllowedToEmailFunc
{
    private static Func<long, EmailPermit> CreatePermit;

    public class EmailPermit
    {
        public static void AllowIssuingPermits()
        {
            IssuegPermit = (long userId) =>
            {
                return new EmailPermit(userId);
            };
        }

        public readonly long UserId;

        private EmailPermit(long userId) 
        {
            UserId = userId;
        }
    }

    static AllowedToEmailFunc()
    {
        EmailPermit.AllowIssuingPermits();
    }

    public static bool AllowedToEmail(UserAndConf user)
    {
        var canEmail = true; /// code checking if we can email the user
        if (canEmail)
        {
            return IssuegPermit(user.UserId);
        }
        else
        {
            return null
        }

    }
}

Cette solution est quelque chose que je ferais un jour régulière au travail, non pas parce que cela conduira à des problèmes dans d'autres endroits, mais parce qu'il est non conventionnel (je ne l'ai jamais vu auparavant) de sorte qu'il pourrait causer d'autres douleurs aux développeurs.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top