Wie zu umschließenden Klasse Zugriff auf verschachteltes Klassenmitglied zu beschränken?

StackOverflow https://stackoverflow.com/questions/1664793

  •  12-09-2019
  •  | 
  •  

Frage

Ist es möglich, die Mitglieder einer verschachtelten Klasse angeben, die von der umgebenden Klasse zugegriffen werden können, aber nicht andere Klassen?

Hier ist eine Darstellung des Problems (natürlich mein eigentlicher Code ein wenig komplexer ist ...):

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; }
    }

    // ...
}

Ich möchte Client-Code verhindern, dass Fälle von JournalEntry schaffen, sondern Journal müssen sie erstellen können. Wenn ich den Konstruktor öffentlich mache, kann jede Instanz erstellen ... aber wenn ich es privat machen, Journal nicht an! Fähig sein

Beachten Sie, dass die JournalEntry Klasse öffentlich sein muss, weil ich in der Lage sein wollen, um vorhandene Einträge zu Client-Code aus.

würde Jeder Vorschlag geschätzt!


UPDATE: Vielen Dank alle für Ihre Eingabe, ich schließlich für die Öffentlichkeit IJournalEntry Schnittstelle ging, von einer privaten JournalEntry Klasse implementiert (trotz der letzten Anforderung in meiner Frage ...)

War es hilfreich?

Lösung

Wenn Ihre Klasse nicht zu komplex ist, können Sie entweder eine Schnittstelle verwenden, die öffentlich sichtbar und machen die tatsächliche Umsetzung der Klasse privat oder Sie können einen geschützten Konstruktor für die JornalEntry Klasse machen und eine eigene Klasse JornalEntryInstance von JornalEntry mit einem öffentlichen abgeleitet haben Konstruktor, die von Ihrem Journal tatsächlich instanziiert wird.

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);
    }
}

Wenn Sie Ihre aktuelle Klasse ist zu komplex, entweder das zu tun, und Sie können mit dem Konstruktor weg nicht vollständig unsichtbar zu sein, können Sie die Konstruktor interner machen, so dass es nur sichtbar in der Montage ist.

Wenn das zu undurchführbar ist, dass Sie immer den Konstruktor privat und Nutzung Reflexion machen es aus Ihrer Zeitschrift Klasse aufrufen:

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

Jetzt, wo ich darüber nachdenke, wäre eine weitere Möglichkeit, einen privaten Delegierten in der enthaltenden Klasse verwenden, die von der inneren Klasse gesetzt

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);
    }
}

Das sollten Sie Ihren gewünschten Sichtbarkeitsebenen geben, ohne auf langsamen Reflexion zurückgreifen, um oder die Einführung zusätzlicher Klassen / Schnittstellen

Andere Tipps

Eigentlich ist es eine vollständige und einfache Lösung für dieses Problem, das nicht den Client-Code beinhaltet nicht ändern oder eine Schnittstelle zu schaffen.

Diese Lösung ist tatsächlich schneller als die Schnittstelle-basierte Lösung für die meisten Fälle und einfacher zu Code.

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

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

Machen Sie JournalEntry einen Privat verschachtelten Typen . Jegliche öffentlichen Mitglieder werden nur an den einschließenden Typen sichtbar sein.

public class Journal
{
    private class JournalEntry
    {
    }
}

Wenn Sie benötigen, um JournalEntry Objekte zur Verfügung zu anderen Klassen zu machen, setzen sie über eine öffentliche Schnittstelle:

public interface IJournalEntry
{
}

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

    private class JournalEntry : IJournalEntry
    {
    }
}

Ein einfacher Ansatz ist es, nur einen internal Konstruktor zu verwenden, aber der Anrufer machen beweisen, wen sie, indem eine Referenz ist, dass nur den berechtigten Anrufer wissen kann (wir nicht sein müssen besorgt nicht-öffentliche Reflexion, denn wenn der Anrufer den Zugang zu nicht-öffentlichen Reflexion hat dann haben wir bereits den Kampf verloren - sie können einen private Konstruktor zugreifen direkt); zum Beispiel:

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);
    }
}

In diesem Fall können Sie entweder:

  1. Machen Sie den Konstruktor intern - diese, die außerhalb dieser Versammlung hält die Schaffung neuer Instanzen oder ...
  2. Umgestalten der JournalEntry Klasse eine öffentliche Schnittstelle zu verwenden, und die tatsächliche JournalEntry Klasse private oder interne zu machen. Die Schnittstelle kann dann für Sammlungen ausgesetzt werden, während die tatsächliche Implementierung versteckt ist.

Ich erwähnte interne als gültige Modifikator oben jedoch je nach Ihren Anforderungen, können private, desto besser geeignet Alternative.

Edit: Sorry, ich privaten Konstruktor erwähnt, aber Sie haben bereits mit diesem Punkt in Ihrer Frage behandelt. Ich entschuldige mich für nicht richtig lesen!

Für allgemeine verschachtelte class =)

Ich weiß, dass dies eine alte Frage ist, und es hat bereits eine akzeptierte Antwort, doch für den Google-Schwimmer, die ein ähnliches Szenario dieser Antwort können verminen kann etwas Hilfe bieten.

Ich kam in dieser Frage ich die gleiche Funktion wie die OP implementieren benötigt. Für mein erstes Szenario diese und diese Antworten funktionierte gut. Trotzdem brauchte ich auch eine verschachtelte generische Klasse zu belichten. Das Problem ist, dass Sie kein Delegattyp Feld (Werksfeld) mit geöffneten generischen Parametern, ohne dass ein eigene Klasse generic aussetzen können, aber offensichtlich ist dies nicht das, was wir wollen, so, hier ist meine Lösung für solches Szenario:

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);
    }
}

Wir haben Boo als unsere interne verschachtelte Klasse mit einem privaten Konstruktor und wir mantain auf unserer übergeordneten Klasse ein Wörterbuch mit diesen generischen Fabriken unter Ausnutzung von dynamischen . Also, jedes Mal TestMeDude genannt wird, Foo sucht, ob die Fabrik für T bereits erstellt wurde, wenn es nicht schafft es verschachtelte Klasse aufrufen statischen Konstruktor.

Test:

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

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

    foo.TestMeDude<string>();

    Console.ReadLine();
}

Die Ausgabe lautet:

Die Lösung Grizzly vorgeschlagen macht es ein bisschen schwer, die verschachtelte Klasse woanders zu schaffen, aber nicht unmöglich, wie Tim Pohlmann noch jemand schrieb es erben kann und den erbenden Klasse ctor verwenden.

Ich bin Vorteil der Tatsache, dass verschachtelte Klasse, um die Container privaten Eigenschaften zugreifen kann, so dass der Behälter fragt schön und die verschachtelte Klasse ermöglicht den Zugriff auf die Ctor.

 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
        }

    }
}

Diese Lösung ist nicht etwas, das ich auf einem normalen Tag auf der Arbeit tun würde, nicht, weil es zu Problemen in anderen Orten führen wird, sondern weil es unkonventionell ist (ich habe es noch nie gesehen), so könnte es andere Entwickler Schmerzen verursachen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top