Domanda

In Il recente post sul blog di Kathleen Dollard, presenta un motivo interessante per utilizzare le classi nidificate in .net.Tuttavia, afferma anche che a FxCop non piacciono le classi nidificate.Presumo che le persone che scrivono le regole di FxCop non siano stupide, quindi ci deve essere un ragionamento dietro quella posizione, ma non sono riuscito a trovarlo.

È stato utile?

Soluzione

Utilizza una classe nidificata quando la classe che stai nidificando è utile solo alla classe che la racchiude.Ad esempio, le classi nidificate ti consentono di scrivere qualcosa come (semplificato):

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

Puoi creare una definizione completa della tua classe in un unico posto, non devi saltare attraverso i passaggi PIMPL per definire come funziona la tua classe e il mondo esterno non ha bisogno di vedere nulla della tua implementazione.

Se la classe TreeNode fosse esterna, dovresti creare tutti i campi public o farne un mucchio get/set metodi per utilizzarlo.Il mondo esterno avrebbe un’altra classe che inquina il suo intellisenso.

Altri suggerimenti

Dal tutorial Java di Sun:

Perché utilizzare le classi nidificate?Esistono diversi motivi convincenti per utilizzare le classi nidificate, tra cui:

  • È un modo per raggruppare logicamente le classi che vengono utilizzate solo in un posto.
  • Aumenta l'incapsulamento.
  • Le classi nidificate possono portare a un codice più leggibile e gestibile.

Raggruppamento logico di classi: se una classe è utile solo a un'altra classe, è logico incorporarla in quella classe e tenere insieme le due.Nidificare tali "classi helper" rende il loro pacchetto più snello.

Maggiore incapsulamento: consideriamo due classi di livello superiore, A e B, in cui B necessita dell'accesso ai membri di A che altrimenti verrebbero dichiarati privati.Nascondendo la classe B all'interno della classe A, i membri di A possono essere dichiarati privati ​​e B può accedervi.Inoltre, B stesso può essere nascosto al mondo esterno. <- Questo non si applica all'implementazione di classi annidate in C#, si applica solo a Java.

Codice più leggibile e gestibile: la nidificazione di piccole classi all'interno di classi di livello superiore posiziona il codice più vicino a dove viene utilizzato.

Modello singleton completamente pigro e thread-safe

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

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

Dipende dall'utilizzo.Raramente utilizzerei una classe nidificata pubblica, ma utilizzo sempre classi nidificate private.Una classe nidificata privata può essere utilizzata per un sottooggetto destinato ad essere utilizzato solo all'interno del genitore.Un esempio di ciò potrebbe essere se una classe HashTable contiene un oggetto Entry privato per archiviare i dati solo internamente.

Se la classe è destinata ad essere utilizzata dal chiamante (esternamente), generalmente mi piace renderla una classe autonoma separata.

Oltre agli altri motivi elencati sopra, c'è un motivo in più a cui mi viene in mente non solo per utilizzare le classi nidificate, ma in realtà classi nidificate pubbliche.Per coloro che lavorano con più classi generiche che condividono gli stessi parametri di tipo generico, la possibilità di dichiarare uno spazio dei nomi generico sarebbe estremamente utile.Sfortunatamente, .Net (o almeno C#) non supporta l'idea di spazi dei nomi generici.Quindi, per raggiungere lo stesso obiettivo, possiamo utilizzare classi generiche per raggiungere lo stesso obiettivo.Prendiamo le seguenti classi di esempio relative a un'entità logica:

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

Possiamo semplificare le firme di queste classi utilizzando uno spazio dei nomi generico (implementato tramite classi nidificate):

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

}

Quindi, attraverso l'uso di classi parziali come suggerito da Erik van Brakel in un commento precedente, è possibile separare le classi in file nidificati separati.Consiglio di utilizzare un'estensione di Visual Studio come NestIn per supportare la nidificazione dei file di classe parziali.Ciò consente di utilizzare i file di classe "spazio dei nomi" anche per organizzare i file di classe nidificati in una cartella.

Per esempio:

Entità.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
{
}

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

    }

}

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

    }

}

Entity.IBaseBusiness.cs

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

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

}

Entity.IBaseDataAccess.cs

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

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

}

I file nel Solution Explorer di Visual Studio verrebbero quindi organizzati come tali:

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

E implementeresti lo spazio dei nomi generico come il seguente:

Utente.cs

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

Utente.DataObject.cs

partial class   User
{

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

}

User.DataObjectList.cs

partial class   User
{

    public  class   DataObjectList : BaseDataObjectList {}

}

Utente.IBusiness.cs

partial class   User
{

    public  interface   IBusiness : IBaseBusiness {}

}

Utente.IDataAccess.cs

partial class   User
{

    public  interface   IDataAccess : IBaseDataAccess {}

}

E i file verrebbero organizzati in Solution Explorer come segue:

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

Quanto sopra è un semplice esempio di utilizzo di una classe esterna come spazio dei nomi generico.In passato ho creato "spazi dei nomi generici" contenenti 9 o più parametri di tipo.Dover mantenere i parametri di tipo sincronizzati tra i nove tipi di cui tutti avevano bisogno per conoscere i parametri di tipo era noioso, soprattutto quando si aggiungeva un nuovo parametro.L'uso di spazi dei nomi generici rende il codice molto più gestibile e leggibile.

Se ho capito bene l'articolo di Katheleen, propone di utilizzare la classe annidata per poter scrivere SomeEntity.Collection invece di EntityCollection< SomeEntity>.Secondo me è un modo controverso per risparmiarti un po' di digitazione.Sono abbastanza sicuro che nel mondo reale le raccolte di applicazioni presenteranno alcune differenze nelle implementazioni, quindi dovrai comunque creare una classe separata.Penso che usare il nome della classe per limitare l'ambito di altre classi non sia una buona idea.Inquina l'intellisenso e rafforza le dipendenze tra le classi.L'uso degli spazi dei nomi è un modo standard per controllare l'ambito delle classi.Tuttavia trovo che l'uso di classi nidificate come nel commento di @hazzen sia accettabile a meno che tu non abbia tonnellate di classi nidificate, il che è un segno di cattiva progettazione.

Un altro utilizzo non ancora menzionato per le classi nidificate è la segregazione dei tipi generici.Si supponga, ad esempio, di voler avere alcune famiglie generiche di classi statiche che possano accettare metodi con diversi numeri di parametri, insieme ai valori per alcuni di tali parametri, e generare delegati con meno parametri.Ad esempio, si desidera avere un metodo statico che possa accettare un file Action<string, int, double> e produrre a String<string, int> che chiamerà l'azione fornita che passa 3.5 come double;si potrebbe anche desiderare di avere un metodo statico che possa accettare un an Action<string, int, double> e produrre un Action<string>, passando 7 come il int E 5.3 come il double.Utilizzando classi nidificate generiche, è possibile fare in modo che le invocazioni dei metodi siano qualcosa del tipo:

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

oppure, poiché gli ultimi tipi in ciascuna espressione possono essere dedotti anche se i primi non possono:

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

L'uso dei tipi generici annidati consente di indicare quali delegati sono applicabili a quali parti della descrizione complessiva del tipo.

Le classi nidificate possono essere utilizzate per le seguenti esigenze:

  1. Classificazione dei dati
  2. Quando la logica della classe principale è complicata e hai la sensazione di aver bisogno di oggetti subordinati per gestire la classe
  3. Quando si capisce che lo stato e l'esistenza della classe dipendono completamente dalla classe che la racchiude

Utilizzo spesso classi nidificate per nascondere i dettagli di implementazione. Un esempio dalla risposta di Eric Lippert qui:

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 { ... }
}

Questo modello diventa ancora migliore con l’uso dei farmaci generici. Vedere questa domanda per due esempi interessanti.Quindi finisco per scrivere

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

invece di

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

Inoltre posso avere un elenco generico di Equality<Person> ma no EqualityComparer<Person, int>

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

mentre

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

non è possibile.Questo è il vantaggio della classe nidificata che eredita dalla classe genitore.

Un altro caso (della stessa natura - implementazione nascosta) è quando si desidera rendere accessibili i membri di una classe (campi, proprietà, ecc.) solo per una singola classe:

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
}

COME nawfal menzionata l'implementazione del modello Abstract Factory, che il codice può essere esteso per ottenere Modello di cluster di classi che si basa sul modello Abstract Factory.

Mi piace nidificare eccezioni uniche per una singola classe, ad es.quelli che non vengono mai gettati da nessun altro posto.

Per esempio:

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

Ciò aiuta a mantenere i file di progetto in ordine e non pieni di un centinaio di piccole classi di eccezioni tozze.

Tieni presente che dovrai testare la classe nidificata.Se è privato, non potrai testarlo isolatamente.

Potresti renderlo interno, però, in concomitanza con il InternalsVisibleTo attributo.Tuttavia, ciò equivarrebbe a creare un campo privato interno solo a scopo di test, cosa che considero una cattiva autodocumentazione.

Pertanto, potresti voler implementare solo classi nidificate private che comportano una bassa complessità.

sì, per questo caso:

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);
        }
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top