Warum/Wann sollte man verwenden von geschachtelten Klassen .net?Oder sollten Sie nicht?

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

  •  09-06-2019
  •  | 
  •  

Frage

In Kathleen Dollard jüngsten blog-post, Sie stellt ein interessanter Grund für die Verwendung von geschachtelten Klassen .net.Aber Sie erwähnt auch, dass FxCop nicht, wie verschachtelte Klassen.Ich gehe davon aus, dass die Menschen schreiben FxCop-Regeln sind nicht dumm, also muss es Gründe, die hinter dieser position, aber ich habe nicht in der Lage zu finden es.

War es hilfreich?

Lösung

Die Verwendung einer geschachtelten Klasse, wenn die Klasse, die Sie nisten ist nur nützlich, um die umschließenden Klasse.Zum Beispiel, geschachtelte Klassen ermöglichen es Ihnen, etwas zu schreiben, wie (vereinfacht):

public class SortedMap {
    private class TreeNode {
        TreeNode left;
        TreeNode right;
    }
}

Sie können eine vollständige definition der Klasse in einem Ort, den Sie nicht haben zu springen durch alle PIMPL Reifen zu definieren, wie Ihre Klasse funktioniert, und die außerhalb Welt nicht braucht um zu sehen, nichts von Ihrer Implementierung ab.

Wenn Sie die TreeNode-Klasse von außen kam, Sie würden entweder müssen alle, die die Felder public oder machen Sie einen Haufen get/set Methoden, es zu benutzen.Die außerhalb Welt hätte eine andere Klasse verschmutzen Ihre intellisense.

Andere Tipps

Von Suns Java-Tutorial:

Warum Verwenden Von Geschachtelten Klassen?Es gibt mehrere zwingende Gründe für die Verwendung von geschachtelten Klassen, darunter:

  • Es ist ein Weg, logisch gruppieren Klassen, die nur an einem Ort.
  • Es erhöht die Kapselung.
  • Geschachtelte Klassen können führen zu besser lesbaren und wartbaren code.

Logische Gruppierung von Klassen Wenn Sie eine Klasse sinnvoll ist nur eine andere Klasse, dann ist es logisch, einbetten, die es in dieser Klasse und halten die beiden zusammen.Nesting solche "Helfer-Klassen" macht Ihre Paket zu straffen.

Erhöhte Kapselung—Betrachten Sie zwei top-level-Klassen, A und B, wobei B benötigt Zugriff auf die Mitglieder Ein, die sonst werden private deklariert.Durch das verstecken der Klasse B in Klasse A, A s-Mitglieder können werden private deklariert und B auf Sie zugreifen können.In Ergänzung, B selbst kann versteckt von der Außenwelt. <- Dies gilt nicht für C#'s Umsetzung von geschachtelten Klassen gilt dies nur für Java.

Mehr lesbaren, wartbaren code—Nesting kleine Klassen innerhalb von Klassen auf oberster Ebene platziert den code näher an, wo es verwendet wird.

Voll Faul und thread-safe-singleton-Muster

public sealed class Singleton
{
    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return Nested.instance;
        }
    }

    class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly Singleton instance = new Singleton();
    }
}

Quelle: http://www.yoda.arachsys.com/csharp/singleton.html

Es hängt von der Nutzung.Ich habe selten würde jemals eine Öffentliche geschachtelte Klasse, aber Private geschachtelte Klassen alle die Zeit.Eine private geschachtelte Klasse kann verwendet werden für eine sub-Objekt, das verwendet werden soll, nur innerhalb der übergeordneten.Ein Beispiel HIERFÜR wäre, wenn ein HashTable-Klasse enthält einen privaten Eintrag Objekt zum speichern von Daten nur intern.

Wenn sich die Klasse dazu genutzt werden, um mit dem Anrufer (extern), ich in der Regel wie machen Sie eine eigenständige Klasse.

Zusätzlich zu den anderen oben genannten Gründen, es ist ein Grund mehr, die ich mir denken kann, nicht nur verschachtelte Klassen, aber in der Tat öffentliche geschachtelte Klassen.Für diejenigen, die die Arbeit mit mehreren generischen Klassen, die die gleichen generischen Typ-Parameter, die Fähigkeit zu erklären, eine generische namespace wäre äußerst nützlich.Leider .Net (oder mindestens C#) unterstützt nicht die Idee, generic namespaces.So in Auftrag zu erreichen das gleiche Ziel, wir können verwenden generische Klassen zu erfüllen das gleiche Ziel.Nehmen Sie das folgende Beispiel Klassen, bezogen auf eine logische Entität:

public  class       BaseDataObject
                    <
                        tDataObject, 
                        tDataObjectList, 
                        tBusiness, 
                        tDataAccess
                    >
        where       tDataObject     : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
        where       tBusiness       : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataAccess     : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}

public  class       BaseDataObjectList
                    <
                        tDataObject, 
                        tDataObjectList, 
                        tBusiness, 
                        tDataAccess
                    >
:   
                    CollectionBase<tDataObject>
        where       tDataObject     : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
        where       tBusiness       : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataAccess     : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}

public  interface   IBaseBusiness
                    <
                        tDataObject, 
                        tDataObjectList, 
                        tBusiness, 
                        tDataAccess
                    >
        where       tDataObject     : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
        where       tBusiness       : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataAccess     : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}

public  interface   IBaseDataAccess
                    <
                        tDataObject, 
                        tDataObjectList, 
                        tBusiness, 
                        tDataAccess
                    >
        where       tDataObject     : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
        where       tBusiness       : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataAccess     : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}

Wir vereinfachen die Signaturen dieser Klassen durch die Verwendung eines generischen namespace (implementiert über geschachtelte Klassen):

public
partial class   Entity
                <
                    tDataObject, 
                    tDataObjectList, 
                    tBusiness, 
                    tDataAccess
                >
        where   tDataObject     : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObject
        where   tDataObjectList : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObjectList, new()
        where   tBusiness       : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseBusiness
        where   tDataAccess     : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseDataAccess
{

    public  class       BaseDataObject {}

    public  class       BaseDataObjectList : CollectionBase<tDataObject> {}

    public  interface   IBaseBusiness {}

    public  interface   IBaseDataAccess {}

}

Dann, durch die Verwendung von partiellen Klassen, wie vorgeschlagen, von Erik van Brakel, die in einem früheren Kommentar, trennen Sie die Klassen in separate verschachtelte Dateien.Ich empfehle, mit einer Visual Studio-Erweiterung wie NestIn zu unterstützen Verschachtelung der partiellen Klasse-Dateien.Dies ermöglicht es der "namespace" class Dateien auch verwendet werden, um organisieren die geschachtelte Klasse der Dateien in einem Ordner wie Weg.

Zum Beispiel:

Einheit.cs

public
partial class   Entity
                <
                    tDataObject, 
                    tDataObjectList, 
                    tBusiness, 
                    tDataAccess
                >
        where   tDataObject     : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObject
        where   tDataObjectList : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObjectList, new()
        where   tBusiness       : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseBusiness
        where   tDataAccess     : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseDataAccess
{
}

Einheit.BaseDataObject.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  class   BaseDataObject
    {

        public  DataTimeOffset  CreatedDateTime     { get; set; }
        public  Guid            CreatedById         { get; set; }
        public  Guid            Id                  { get; set; }
        public  DataTimeOffset  LastUpdateDateTime  { get; set; }
        public  Guid            LastUpdatedById     { get; set; }

        public
        static
        implicit    operator    tDataObjectList(DataObject dataObject)
        {
            var returnList  = new tDataObjectList();
            returnList.Add((tDataObject) this);
            return returnList;
        }

    }

}

Einheit.BaseDataObjectList.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  class   BaseDataObjectList : CollectionBase<tDataObject>
    {

        public  tDataObjectList ShallowClone() 
        {
            var returnList  = new tDataObjectList();
            returnList.AddRange(this);
            return returnList;
        }

    }

}

Einheit.IBaseBusiness.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  interface   IBaseBusiness
    {
        tDataObjectList Load();
        void            Delete();
        void            Save(tDataObjectList data);
    }

}

Einheit.IBaseDataAccess.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  interface   IBaseDataAccess
    {
        tDataObjectList Load();
        void            Delete();
        void            Save(tDataObjectList data);
    }

}

Die Dateien im visual studio-Projektmappen-explorer mit der würde dann organisiert werden als solche:

Entity.cs
+   Entity.BaseDataObject.cs
+   Entity.BaseDataObjectList.cs
+   Entity.IBaseBusiness.cs
+   Entity.IBaseDataAccess.cs

Und Sie würden Umsetzung des generic-namespace wie die folgenden:

Benutzer.cs

public
partial class   User
:
                Entity
                <
                    User.DataObject, 
                    User.DataObjectList, 
                    User.IBusiness, 
                    User.IDataAccess
                >
{
}

Benutzer.DataObject.cs

partial class   User
{

    public  class   DataObject : BaseDataObject 
    {
        public  string  UserName            { get; set; }
        public  byte[]  PasswordHash        { get; set; }
        public  bool    AccountIsEnabled    { get; set; }
    }

}

Benutzer.DataObjectList.cs

partial class   User
{

    public  class   DataObjectList : BaseDataObjectList {}

}

Benutzer.IBusiness.cs

partial class   User
{

    public  interface   IBusiness : IBaseBusiness {}

}

Benutzer.IDataAccess.cs

partial class   User
{

    public  interface   IDataAccess : IBaseDataAccess {}

}

Und die Dateien organisiert werden sollen, in dem Projektmappen-explorer wie folgt:

User.cs
+   User.DataObject.cs
+   User.DataObjectList.cs
+   User.IBusiness.cs
+   User.IDataAccess.cs

Das obige ist ein einfaches Beispiel für die Verwendung einer äußeren Klasse als ein generic-namespace.Ich habe gebaut "generic namespaces" mit 9 oder mehr Typ-Parameter in die Vergangenheit.Dass man immer den Typ-Parametern synchronisiert, in den neun Arten, die alle gebraucht werden, um die Parameter des Typs war mühsam, vor allem, wenn um einen neuen parameter hinzuzufügen.Die Verwendung von generischen namespaces macht code viel mehr überschaubar und lesbar.

Wenn ich verstehe Katheleen ist der Artikel richtig, Sie schlägt verschachtelte Klasse schreiben zu können SomeEntity.Sammlung statt EntityCollection< SomeEntity>.Meiner Meinung nach ist es Kontroverse Weise sparen Sie einiges an schreibarbeit.Ich bin mir ziemlich sicher, dass in der realen Welt Anwendung, die Sammlungen werden haben einige Unterschied in Implementierungen, so dass Sie brauchen, um erstellen Sie separate Klasse sowieso.Ich denke, dass mit dem Klassennamen zu begrenzen anderen Klasse Umfang ist nicht eine gute Idee.Es verschmutzt intellisense und zu stärken, Abhängigkeiten zwischen Klassen.Verwendung von namespaces ist eine standard-Art der Kontrolle-Klassen-scope.Allerdings finde ich, dass der Einsatz von geschachtelten Klassen, wie in @hazzen Kommentar akzeptabel ist, es sei denn, Sie haben Tonnen von geschachtelten Klassen, das ist ein Zeichen für schlechtes design.

Eine weitere Verwendung noch nicht erwähnt, die verschachtelte Klassen ist die Trennung von generischen Typen.Zum Beispiel, angenommen, man will auf einige generische Familien von statischen Klassen, die Sie treffen können Methoden mit einer unterschiedlichen Anzahl von Parametern, zusammen mit den Werten für einige der Parameter, und generieren die Teilnehmer mit weniger Parametern.Für Beispiel, eine Wünsche haben, eine statische Methode, die eine Action<string, int, double> und die Ausbeute eines String<string, int> die nennen das mitgelieferte Aktion vorbei 3.5 als double;man kann auf Wunsch auch eine statische Methode, die eine Action<string, int, double> und Ertrag ein Action<string>, vorbei 7 als die int und 5.3 als die double.Die Verwendung von generischen verschachtelte Klassen, kann man vereinbaren, um die Methodenaufrufe so etwas wie:

MakeDelegate<string,int>.WithParams<double>(theDelegate, 3.5);
MakeDelegate<string>.WithParams<int,double>(theDelegate, 7, 5.3);

oder, weil der letztere Arten in jedem Ausdruck abgeleitet werden kann, obwohl die ersteren nicht:

MakeDelegate<string,int>.WithParams(theDelegate, 3.5);
MakeDelegate<string>.WithParams(theDelegate, 7, 5.3);

Mit dem verschachtelten generischen Typen, die es möglich macht, sagen die Delegierten sind anwendbar, zu dem Teile der gesamten Typ Beschreibung.

Die geschachtelte Klassen können verwendet werden, für die folgenden Bedürfnisse:

  1. Klassifizierung der Daten
  2. Wenn die Logik der main-Klasse ist kompliziert, und Sie fühlen, wie Sie benötigen untergeordneten Objekte zum verwalten der Klasse
  3. Wenn Sie, dass der Zustand und das Vorhandensein der Klasse hängt komplett von der einschließenden Klasse

Häufig verwende ich verschachtelte Klassen zu verstecken detail. Ein Beispiel von Eric Lippert ' s Antwort hier:

abstract public class BankAccount
{
    private BankAccount() { }
    // Now no one else can extend BankAccount because a derived class
    // must be able to call a constructor, but all the constructors are
    // private!
    private sealed class ChequingAccount : BankAccount { ... }
    public static BankAccount MakeChequingAccount() { return new ChequingAccount(); }
    private sealed class SavingsAccount : BankAccount { ... }
}

Dieses Muster wird noch besser mit der Verwendung von Generika. Finden diese Frage für zwei Coole Beispiele.So habe ich am Ende schreiben

Equality<Person>.CreateComparer(p => p.Id);

statt

new EqualityComparer<Person, int>(p => p.Id);

Auch kann ich eine generische Liste Equality<Person> aber nicht EqualityComparer<Person, int>

var l = new List<Equality<Person>> 
        { 
         Equality<Person>.CreateComparer(p => p.Id),
         Equality<Person>.CreateComparer(p => p.Name) 
        }

wo, wie

var l = new List<EqualityComparer<Person, ??>>> 
        { 
         new EqualityComparer<Person, int>>(p => p.Id),
         new EqualityComparer<Person, string>>(p => p.Name) 
        }

ist nicht möglich.Das ist der Vorteil von verschachtelten Klasse erbt von der übergeordneten Klasse.

Ein weiterer Fall (von der gleichen Art - verstecken Umsetzung) ist, wenn Sie wollen, um die Mitglieder einer Klasse (Felder, Eigenschaften usw.) zugänglich nur für eine einzige Klasse:

public class Outer 
{
   class Inner //private class
   {
       public int Field; //public field
   }

   static inner = new Inner { Field = -1 }; // Field is accessible here, but in no other class
}

Als nawfal erwähnt Umsetzung des Abstract Factory-Muster, der code kann axtended zu erreichen Klasse-Cluster-Muster basierend auf Abstrakte-Fabrik-Muster.

Ich mag nest Ausnahmen, die nur für eine einzige Klasse, dh.diejenigen, die noch nie geworfen jeder andere Ort.

Zum Beispiel:

public class MyClass
{
    void DoStuff()
    {
        if (!someArbitraryCondition)
        {
            // This is the only class from which OhNoException is thrown
            throw new OhNoException(
                "Oh no! Some arbitrary condition was not satisfied!");
        }
        // Do other stuff
    }

    public class OhNoException : Exception
    {
        // Constructors calling base()
    }
}

Dies hilft, Ihre Projekt-Dateien sauber und nicht voll von hundert stubby little exception-Klassen.

Beachten Sie, dass Sie werden müssen test die geschachtelte Klasse.Wenn es privat ist, werden Sie nicht in der Lage, um es zu testen in isolation.

Sie könnten es interne, obwohl, in Verbindung mit der InternalsVisibleTo Attribut.Dies würde jedoch die gleichen wie bei einer privaten Feld nur intern für Testzwecke, die ich als schlecht selbst-Dokumentation.

Also, möchten Sie möglicherweise nur die Implementierung von private geschachtelte Klassen mit geringer Komplexität.

ja, für diesen Fall:

class Join_Operator
{

    class Departamento
    {
        public int idDepto { get; set; }
        public string nombreDepto { get; set; }
    }

    class Empleado
    {
        public int idDepto { get; set; }
        public string nombreEmpleado { get; set; }
    }

    public void JoinTables()
    {
        List<Departamento> departamentos = new List<Departamento>();
        departamentos.Add(new Departamento { idDepto = 1, nombreDepto = "Arquitectura" });
        departamentos.Add(new Departamento { idDepto = 2, nombreDepto = "Programación" });

        List<Empleado> empleados = new List<Empleado>();
        empleados.Add(new Empleado { idDepto = 1, nombreEmpleado = "John Doe." });
        empleados.Add(new Empleado { idDepto = 2, nombreEmpleado = "Jim Bell" });

        var joinList = (from e in empleados
                        join d in departamentos on
                        e.idDepto equals d.idDepto
                        select new
                        {
                            nombreEmpleado = e.nombreEmpleado,
                            nombreDepto = d.nombreDepto
                        });
        foreach (var dato in joinList)
        {
            Console.WriteLine("{0} es empleado del departamento de {1}", dato.nombreEmpleado, dato.nombreDepto);
        }
    }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top