Private innere Klassen in C # - warum werden sie nicht häufiger verwendet?

76

Ich bin relativ neu in C # und jedes Mal, wenn ich anfange, an einem C # -Projekt zu arbeiten (ich habe nur an fast ausgereiften Projekten in C # gearbeitet), frage ich mich, warum es keine inneren Klassen gibt.

Vielleicht verstehe ich ihr Ziel nicht. Für mich sehen innere Klassen - zumindest private innere Klassen - sehr nach "inneren Prozeduren" in Pascal / Modula-2 / Ada aus: Sie ermöglichen es, eine Hauptklasse in kleinere Teile aufzuteilen, um das Verständnis zu erleichtern.

Beispiel: Hier ist, was die meiste Zeit zu sehen ist:

public class ClassA
{
   public MethodA()
   {
      <some code>
      myObjectClassB.DoSomething(); // ClassB is only used by ClassA
      <some code>
   }
}

public class ClassB
{
   public DoSomething()
   {
   }
}

Da ClassB (zumindest für eine Weile) nur von ClassA verwendet wird, gehe ich davon aus, dass dieser Code besser wie folgt ausgedrückt werden sollte:

   public class ClassA
   {
      public MethodA()
      {
         <some code>
         myObjectClassB.DoSomething(); // Class B is only usable by ClassA
         <some code>
      }

      private class ClassB
      {
         public DoSomething()
         {
         }
      }
   }

Ich würde mich freuen, von Ihnen zu diesem Thema zu hören - Habe ich recht?

Sylvain Rodrigue
quelle
1
Beachten Sie auch, dass Sie keine Erweiterungsmethoden in verschachtelten Klassen haben können.
Nawfal

Antworten:

80

Verschachtelte Klassen (wahrscheinlich am besten, um das Wort "inner" zu vermeiden, da verschachtelte Klassen in C # etwas anders sind als innere Klassen in Java) können in der Tat sehr nützlich sein.

Ein Muster, das nicht erwähnt wurde, ist das "bessere Aufzählungsmuster" - das noch flexibler sein kann als das in Java:

public abstract class MyCleverEnum
{
    public static readonly MyCleverEnum First = new FirstCleverEnum();
    public static readonly MyCleverEnum Second = new SecondCleverEnum();

    // Can only be called by this type *and nested types*
    private MyCleverEnum()
    {
    }

    public abstract void SomeMethod();
    public abstract void AnotherMethod();

    private class FirstCleverEnum : MyCleverEnum
    {
        public override void SomeMethod()
        {
             // First-specific behaviour here
        }

        public override void AnotherMethod()
        {
             // First-specific behaviour here
        }
    }

    private class SecondCleverEnum : MyCleverEnum
    {
        public override void SomeMethod()
        {
             // Second-specific behaviour here
        }

        public override void AnotherMethod()
        {
             // Second-specific behaviour here
        }
    }
}

Wir könnten eine gewisse Sprachunterstützung gebrauchen, um einige davon automatisch zu erledigen - und es gibt viele Optionen, die ich hier nicht gezeigt habe, z. B. nicht für alle Werte eine verschachtelte Klasse oder für mehrere Werte dieselbe verschachtelte Klasse zu verwenden. aber ihnen verschiedene Konstruktorparameter geben. Grundsätzlich bietet die Tatsache, dass die verschachtelte Klasse den privaten Konstruktor aufrufen kann, viel Leistung.

Jon Skeet
quelle
11
Sehr schlau! Aber ich werde es niemals benutzen. Software-Buchautoren und ihre Leser (!) Sind oft kluge Völker. Sie denken an viele helle Dinge und es ist cool. Aber Software-Betreuer sind normale Leute, die Gamma / Skeet nicht gelesen haben. Mein Gast: Je klüger es ist, desto weniger ist es wartbar ...
Sylvain Rodrigue
29
Das ist wahr, bis es ein erkanntes Muster wird. Es ist wie "while ((line = streamReader.ReadLine ())! = Null)" sieht zunächst schlecht aus - Nebenwirkungen in einer Weile! Aber wenn es idiomatisch ist, überwindet man diesen Geruch und schätzt die Prägnanz.
Jon Skeet
2
@ Jon, danke! Ich glaube, ich habe es jetzt verstanden. MyCleverEnumfungiert sowohl als Inhaber der Enum-Mitglieder als auch als Schnittstelle jedes Enum-Mitglieds und ist daher prägnanter als andere Lösungen. Durch die Verwendung eingebetteter Klassen wird sichergestellt, dass die Unterstützungsklassen außerhalb der Klasse verborgen sind. Der private Konstruktor bewirkt, dass sich die Klasse von außerhalb der Klasse wie eine statische / Singleton-Klasse verhält, von innen jedoch wie eine abstrakte Basisklasse.
Devuxer
1
Eine Sache, die ich bei der Verwendung von Klassenumfang so schwierig fand, ist, dass die öffentlichen inneren Klassen am Ende "gepunktete" öffentliche Namen haben. Gibt es eine saubere Möglichkeit, eine öffentlich zugängliche Klasse den gleichen Umfang wie eine verschachtelte Klasse zu haben, aber über einen "einfachen" Namen öffentlich zugänglich zu sein?
Supercat
1
@supercat, Sie können eine using-Direktive oben in die Datei einfügen using FirstCleverEnum = MyCleverEnum.FirstCleverEnum. Dies kann das Problem mindern, aber es gibt keine Möglichkeit, diesen Alias ​​zu "exportieren".
Marty Neal
31

Die Framework Design Guidelines enthalten die besten Regeln für die Verwendung verschachtelter Klassen, die ich bisher gefunden habe.

Hier ist eine kurze zusammenfassende Liste:

  1. Verwenden Sie verschachtelte Typen, wenn die Beziehung zwischen Typ und verschachteltem Typ so ist, dass die Semantik für die Barrierefreiheit von Mitgliedern gewünscht wird.

  2. Verwenden Sie KEINE öffentlich verschachtelten Typen als logisches Gruppenkonstrukt

  3. Vermeiden Sie öffentlich exponierte verschachtelte Typen.

  4. Verwenden Sie KEINE verschachtelten Typen, wenn der Typ wahrscheinlich außerhalb des enthaltenen Typs referenziert wird.

  5. Verwenden Sie KEINE verschachtelten Typen, wenn diese durch Clientcode instanziiert werden müssen.

  6. Definieren Sie KEINEN verschachtelten Typ als Mitglied einer Schnittstelle.

matt_dev
quelle
1
Einige davon sind ziemlich offensichtlich (wie 4 und 5). Aber können Sie Gründe nennen, warum die anderen Regeln empfohlen werden?
Peter Bagnall
@PeterBagnall ist es nur ich oder 6. widerspricht der akzeptierten Antwort?
jungle_mole
@jungle_mole Ein verschachtelter Typ, der eine öffentliche Schnittstelle implementiert, ist in Ordnung und üblich. Der Typ der verschachtelten Typen sollte niemals öffentlich verfügbar gemacht werden.
Erick
@Erick yep. Ich hatte gerade zwei Teile von zwei "Designs" in meinem Kopf getauscht. Die Gründe für diesen 6. liegen auf der Hand. was ich überhaupt gedacht habe
jungle_mole
12

Sie sollten die Verantwortlichkeiten jeder Klasse einschränken, damit jede Klasse einfach, testbar und wiederverwendbar bleibt . Private innere Klassen gehen dagegen vor. Sie tragen zur Komplexität der äußeren Klasse bei, sind nicht testbar und nicht wiederverwendbar.

Wim Coenen
quelle
Einfach, testbar und wiederverwendbar sind sehr gut. Aber was ist mit der Kapselung? Wenn etwas draußen nicht zu sehen ist, warum sollte es dann veröffentlicht werden? In meinem Buch können innere Klassen das Ganze etwas einfacher machen. Und sie sind testbar (mit relexion)!
Sylvain Rodrigue
4
Ich würde die Kapselung als Argument gegen innere Klassen wählen . Innere Klassen können auf private Mitglieder ihrer äußeren Klassen zugreifen!
Wim Coenen
Ihr Recht ! Wenn eine innere Klasse eine Art Kopplung zwischen ihr und der äußeren Klasse hinzufügt - ich habe es zuerst nicht gesehen - sorry. Und danke !
Sylvain Rodrigue
9
Ich sehe, dass Programmierer ständig Datenstrukturen wie Tupel anstelle von Privatklassen verwenden. Ich würde definitiv argumentieren, dass eine private Klasse zu Code führt, der einfacher zu lesen und weniger fehleranfällig ist. Das Argument, dass es die Kapselung verletzt, ist nicht richtig. Es gibt nichts an einer privaten Klasse, das die Kapselung verletzt. Es ist in einer Klasse enthalten und sollte und sollte alle Rechte eines anderen Mitglieds der äußeren Klasse haben.
Mark
Wie bei allem kommt es darauf an. Und in diesem Fall denke ich, dass es von den Besonderheiten der verschachtelten Klasse abhängt. Wenn Sie beispielsweise ein dto innerhalb einer übergeordneten Klasse erstellen (z. B. um Methodensignaturen sauber zu halten), müssen Sie nichts testen. Es ist nicht anders als das Erstellen von privaten Tupeln imo, nur klarer. Wenn Sie jedoch eine reichhaltige verschachtelte Klasse mit Funktionen erstellen, haben Sie eine schlechte Zeit.
Sinaesthetic
3

Für mich persönlich erstelle ich private innere Klassen nur, wenn ich in Bearbeitung befindliche Sammlungen eines Objekts erstellen muss, für die möglicherweise Methoden erforderlich sind.

Andernfalls kann es für andere Entwickler, die an dem Projekt arbeiten, zu Verwirrung kommen, diese Klassen tatsächlich zu finden, da sie nicht genau wissen, wo sie sich befinden.

Tom Anderson
quelle
1
Da es sich jedoch um private Klassen handelt, sollten andere Entwickler diese nicht kennen, es sei denn, sie müssen natürlich die äußere Klasse beibehalten. Natürlich kann dies ein Problem sein, wenn die Menschen sich privater innerer Klassen nicht bewusst sind. Danke für deine Antwort !
Sylvain Rodrigue
16
Wenn das meine Entwickler verwirren würde, würde ich neue Entwickler bekommen.
DancesWithBamboo