Pregunta

En Kathleen Dollard reciente post en el blog, ella presenta una interesante razón para el uso de las clases anidadas en .net.Sin embargo, también menciona que FxCop no le gusta clases anidadas.Estoy asumiendo que la gente que escribe FxCop reglas no son tontos, así que debe de ser el razonamiento detrás de esa posición, pero no he sido capaz de encontrarlo.

¿Fue útil?

Solución

El uso de una clase anidada cuando la clase está de anidación sólo es útil para la clase envolvente.Por ejemplo, las clases anidadas permiten escribir algo como (simplificado):

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

Usted puede hacer una definición completa de su clase en un solo lugar, usted no tiene que saltar a través de cualquier PIMPL aros para definir cómo su clase de obras, y el mundo exterior no necesita ver nada de su implementación.

Si la clase TreeNode, es exterior, se tiene que hacer todos los campos public o hacer un montón de get/set los métodos a utilizar.El mundo exterior que habría otra clase de contaminante sus intellisense.

Otros consejos

De Sun Java Tutorial:

¿Por Qué Utilizar Clases Anidadas?Hay varias razones de peso para el uso de las clases anidadas, entre ellos:

  • Es una manera de la agrupación lógica de las clases que se utilizan únicamente en un solo lugar.
  • Aumenta la encapsulación.
  • Clases anidadas puede conducir a más legible y mantenible código.

Agrupación lógica de clases—Si una clase es útil sólo una otra clase, entonces es lógico que se incrusta en la clase y mantener a los dos juntos.De anidación de tales "ayudante de clases" que hace que su paquete más ágil.

El aumento de encapsulación—Considerar dos clases de nivel superior, a y B, donde B necesidades de acceso a los miembros de Una que de otra manera sería declarado privado.Escondiéndose de la clase B a la clase A, a los miembros pueden ser declarada privada y B pueden acceder a ellos.Además, B, sí puede ser oculto del mundo exterior. <- Esto no se aplica a C#'s de la implementación de las clases anidadas, esto sólo se aplica a Java.

Más legible, mantenible código de Anidación de las pequeñas clases dentro de clases de nivel superior coloca el código más cerca de donde se utiliza.

Totalmente Perezoso y subprocesos patrón singleton

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

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

Depende del uso.Yo rara vez nunca usaría un Público de clase anidada pero el uso Privado de las clases anidadas todo el tiempo.Privado de la clase anidada puede ser utilizado para un sub-objeto que está destinado a ser utilizado sólo en el interior de la matriz.Un ejemplo de esto sería si la clase HashTable contiene una Entrada privada objeto para almacenar datos sólo internamente.

Si la clase está destinado a ser utilizado por la persona que llama (externamente), en general me gusta lo que es un autónomo independiente de la clase.

Además de las otras razones mencionadas anteriormente, hay una razón más que puedo pensar, no sólo para uso de las clases anidadas, pero de hecho públicas las clases anidadas.Para aquellos que trabajan con múltiples clases genéricas que comparten los mismos parámetros de tipo genérico, la capacidad para declarar un espacio de nombres genéricos sería extremadamente útil.Por desgracia, .Neto (o, al menos, C#) no apoya la idea de espacios de nombres genéricos.Así, en orden a lograr el mismo objetivo, se pueden utilizar las clases genéricas para cumplir con el mismo objetivo.Tomemos el siguiente ejemplo clases relacionadas con una entidad lógica:

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

Podemos simplificar las firmas de estas clases mediante el uso de un espacio de nombre genérico (implementado a través de las clases anidadas):

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

}

Entonces, a través del uso de clases parciales según lo sugerido por Erik van Brakel en un comentario anterior, puede separar las clases en distintos archivos anidados.Recomiendo el uso de una extensión de Visual Studio como Nestina para el apoyo de anidación de la clase parcial de los archivos.Esto permite que el "espacio de nombres" archivos de clase a también ser utilizados para organizar la clase anidada de los archivos en una carpeta de igual manera.

Por ejemplo:

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

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

    }

}

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

    }

}

Entidad.IBaseBusiness.cs

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

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

}

Entidad.IBaseDataAccess.cs

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

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

}

Los archivos en el explorador de soluciones de visual studio, entonces sería organizadas como tales:

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

Y usted podría implementar el espacio de nombre genérico como el siguiente:

Usuario.cs

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

Usuario.DataObject.cs

partial class   User
{

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

}

Usuario.DataObjectList.cs

partial class   User
{

    public  class   DataObjectList : BaseDataObjectList {}

}

Usuario.IBusiness.cs

partial class   User
{

    public  interface   IBusiness : IBaseBusiness {}

}

Usuario.IDataAccess.cs

partial class   User
{

    public  interface   IDataAccess : IBaseDataAccess {}

}

Y los archivos se organizan en el explorador de soluciones de la siguiente manera:

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

El de arriba es un ejemplo sencillo de uso de una clase exterior como un espacio de nombre genérico.He construido "genérico espacios de nombres" que contiene 9 o más parámetros de tipo en el pasado.Tener que mantener los parámetros de tipo sincronizados a través de los nueve tipos que todos necesitan saber los parámetros de tipo era tedioso, especialmente cuando la adición de un nuevo parámetro.El uso de espacios de nombres genéricos hace que el código mucho más manejable y fácil de leer.

Si entiendo Katheleen el artículo de la derecha, que propone el uso de una clase anidada para ser capaz de escribir SomeEntity.Colección en lugar de EntityCollection< SomeEntity>.En mi opinión es controversial forma de ahorrar algo de tipeo.Estoy bastante seguro de que en el mundo real de la aplicación de las colecciones tendrá alguna diferencia en las implementaciones, por lo que tendrá que crear clase separada de todos modos.Yo creo que el uso de un nombre de clase para limitar el otro ámbito de la clase no es una buena idea.Contamina intellisense y fortalecer las dependencias entre clases.El uso de espacios de nombres es una manera estándar de las clases de control de alcance.Sin embargo, me parece que el uso de las clases anidadas como en @hazzen comentario es aceptable a menos que usted tiene toneladas de clases anidadas que es un signo de mal diseño.

Otro uso aún no se mencionó para las clases anidadas es la segregación de los tipos genéricos.Por ejemplo, supongamos que uno quiere tener algunas familias genéricas de las clases estáticas que puede tomar los métodos con diferentes números de parámetros, junto con los valores para algunos de los parámetros, y generar los delegados con menos parámetros.Por ejemplo, desea disponer de un método estático que puede tomar un Action<string, int, double> y el rendimiento de un String<string, int> que se llame a la suministra la acción de pasar de 3.5 como el double;uno también puede tener un método estático que puede tomar un Action<string, int, double> y el rendimiento de una Action<string>, pasando 7 como el int y 5.3 como el double.El uso genérico de las clases anidadas, uno puede hacer arreglos para que el método de invocaciones a ser algo así como:

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

o, debido a que los últimos tipos en cada expresión puede deducirse aunque los antiguos no pueden:

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

El uso de los tipos genéricos anidados hace posible decir que los delegados son aplicables a qué partes del total de la descripción del tipo.

Las clases anidadas puede ser utilizado para las siguientes necesidades:

  1. Clasificación de los datos
  2. Cuando la lógica de la clase principal es complicado y usted se siente como usted requieren objetos subordinados a manejar la clase
  3. Cuando usted que el estado y la existencia de la clase depende completamente de la clase envolvente

Yo uso a menudo de las clases anidadas para ocultar los detalles de ejecución. Un ejemplo de Eric Lippert la respuesta aquí:

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

Este patrón se vuelve aún mejor con el uso de medicamentos genéricos. Ver esta pregunta para dos fresco ejemplos.Así que al final me la escritura

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

en lugar de

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

También puedo tener una lista genérica de Equality<Person> pero no EqualityComparer<Person, int>

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

donde como

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

no es posible.Ese es el beneficio de la clase anidada que hereda de la clase principal.

Otro caso de la misma naturaleza - ocultación de la aplicación) es cuando se quiere hacer que los miembros de una clase (campos, propiedades, etc) accesible sólo para una sola clase:

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
}

Como nawfal mencionó la implementación de Resumen patrón de Fábrica, ya que el código puede ser axtended para lograr Clase de Agrupaciones patrón que se basa en Abstracto patrón de Fábrica.

Me gusta nido excepciones que son exclusivas de una sola clase, es decir,.que nunca son lanzados desde cualquier otro lugar.

Por ejemplo:

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

Esto ayuda a mantener los archivos de proyecto ordenado y no se llena de un centenar de rastrojo poco de clases de excepción.

Tenga en cuenta que usted necesita para probar la clase anidada.Si es privado, usted no será capaz de probarlo en el aislamiento.

Usted podría hacer es interna, aunque, en conjunción con el InternalsVisibleTo atributo.Sin embargo, esto sería lo mismo que hacer un campo privado interno sólo para propósitos de prueba, que yo considero malo auto-documentación.

Así, puede que desee poner en práctica sólo privada de las clases anidadas que involucran baja complejidad.

sí, para este 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);
        }
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top