Pergunta

Em Postagem recente no blog de Kathleen Dollard, ela apresenta um motivo interessante para usar classes aninhadas em .net.No entanto, ela também menciona que o FxCop não gosta de classes aninhadas.Presumo que as pessoas que escrevem as regras do FxCop não sejam estúpidas, então deve haver um raciocínio por trás dessa posição, mas não consegui encontrá-lo.

Foi útil?

Solução

Use uma classe aninhada quando a classe que você está aninhando for útil apenas para a classe envolvente.Por exemplo, classes aninhadas permitem escrever algo como (simplificado):

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

Você pode fazer uma definição completa de sua classe em um só lugar, não precisa passar por nenhum obstáculo do PIMPL para definir como sua classe funciona e o mundo externo não precisa ver nada de sua implementação.

Se a classe TreeNode fosse externa, você teria que criar todos os campos public ou fazer um monte de get/set métodos para usá-lo.O mundo exterior teria outra classe poluindo seu intellisense.

Outras dicas

Do tutorial Java da Sun:

Por que usar classes aninhadas?Existem vários motivos convincentes para usar classes aninhadas, entre eles:

  • É uma forma de agrupar logicamente classes que são utilizadas apenas em um local.
  • Aumenta o encapsulamento.
  • Classes aninhadas podem levar a um código mais legível e de fácil manutenção.

Agrupamento lógico de classes — Se uma classe é útil apenas para uma outra classe, então é lógico incorporá-la nessa classe e manter as duas juntas.Aninhar essas "classes auxiliares" torna seu pacote mais simplificado.

Maior encapsulamento – Considere duas classes de nível superior, A e B, onde B precisa de acesso a membros de A que de outra forma seriam declarados privados.Ao ocultar a classe B dentro da classe A, os membros de A podem ser declarados privados e B pode acessá-los.Além disso, o próprio B pode ser escondido do mundo exterior. <- Isso não se aplica à implementação de classes aninhadas em C#, apenas se aplica a Java.

Código mais legível e de fácil manutenção: aninhar classes pequenas em classes de nível superior coloca o código mais próximo de onde ele é usado.

Padrão singleton totalmente preguiçoso e seguro para threads

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

Depende do uso.Eu raramente usaria uma classe aninhada pública, mas usaria classes aninhadas privadas o tempo todo.Uma classe aninhada privada pode ser usada para um subobjeto que se destina a ser usado apenas dentro do pai.Um exemplo disso seria se uma classe HashTable contivesse um objeto Entry privado para armazenar dados apenas internamente.

Se a classe for usada pelo chamador (externamente), geralmente gosto de torná-la uma classe independente separada.

Além dos outros motivos listados acima, há mais um motivo em que consigo pensar não apenas para usar classes aninhadas, mas de fato classes aninhadas públicas.Para aqueles que trabalham com múltiplas classes genéricas que compartilham os mesmos parâmetros de tipo genérico, a capacidade de declarar um namespace genérico seria extremamente útil.Infelizmente, .Net (ou pelo menos C#) não suporta a ideia de namespaces genéricos.Portanto, para atingir o mesmo objetivo, podemos usar classes genéricas para cumprir o mesmo objetivo.Tomemos os seguintes exemplos de classes relacionadas a uma entidade 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 as assinaturas dessas classes usando um namespace genérico (implementado por meio de classes aninhadas):

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

}

Então, através do uso de classes parciais, conforme sugerido por Erik van Brakel em um comentário anterior, você pode separar as classes em arquivos aninhados separados.Eu recomendo usar uma extensão do Visual Studio como NestIn para oferecer suporte ao aninhamento de arquivos de classe parciais.Isso permite que os arquivos de classe "namespace" também sejam usados ​​para organizar os arquivos de classe aninhados em uma pasta.

Por exemplo:

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

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

    }

}

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

    }

}

Entidade.IBaseBusiness.cs

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

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

}

Entidade.IBaseDataAccess.cs

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

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

}

Os arquivos no explorador de soluções do visual studio seriam então organizados da seguinte forma:

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

E você implementaria o namespace genérico da seguinte forma:

Usuário.cs

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

Usuário.DataObject.cs

partial class   User
{

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

}

Usuário.DataObjectList.cs

partial class   User
{

    public  class   DataObjectList : BaseDataObjectList {}

}

Usuário.IBusiness.cs

partial class   User
{

    public  interface   IBusiness : IBaseBusiness {}

}

Usuário.IDataAccess.cs

partial class   User
{

    public  interface   IDataAccess : IBaseDataAccess {}

}

E os arquivos seriam organizados no explorador de soluções da seguinte forma:

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

O exemplo acima é um exemplo simples de uso de uma classe externa como um namespace genérico.Eu construí "namespaces genéricos" contendo 9 ou mais parâmetros de tipo no passado.Ter que manter esses parâmetros de tipo sincronizados entre os nove tipos que todos precisavam para saber os parâmetros de tipo era entediante, especialmente ao adicionar um novo parâmetro.O uso de namespaces genéricos torna esse código muito mais gerenciável e legível.

Se bem entendi o artigo de Katheleen, ela propõe usar uma classe aninhada para poder escrever SomeEntity.Collection em vez de EntityCollection<SomeEntity>.Na minha opinião, é uma maneira controversa de economizar digitação.Tenho certeza de que no mundo real as coleções de aplicativos terão alguma diferença nas implementações, então você precisará criar uma classe separada de qualquer maneira.Acho que usar o nome da classe para limitar o escopo de outra classe não é uma boa ideia.Polui o intellisense e fortalece as dependências entre as classes.Usar namespaces é uma maneira padrão de controlar o escopo das classes.No entanto, acho que o uso de classes aninhadas como no comentário @hazzen é aceitável, a menos que você tenha toneladas de classes aninhadas, o que é um sinal de design ruim.

Outro uso ainda não mencionado para classes aninhadas é a segregação de tipos genéricos.Por exemplo, suponha que alguém queira ter algumas famílias genéricas de classes estáticas que possam receber métodos com vários números de parâmetros, juntamente com valores para alguns desses parâmetros, e gerar delegados com menos parâmetros.Por exemplo, deseja-se ter um método estático que possa assumir um Action<string, int, double> e render um String<string, int> que chamará a ação fornecida passando 3,5 como o double;pode-se também desejar ter um método estático que possa assumir um Action<string, int, double> e render um Action<string>, passando 7 Enquanto o int e 5.3 Enquanto o double.Usando classes aninhadas genéricas, pode-se fazer com que as invocações de método sejam algo como:

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

ou, porque os últimos tipos em cada expressão podem ser inferidos mesmo que os primeiros não possam:

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

O uso de tipos genéricos aninhados possibilita saber quais delegados são aplicáveis ​​a quais partes da descrição geral do tipo.

As classes aninhadas podem ser usadas para as seguintes necessidades:

  1. Classificação dos dados
  2. Quando a lógica da classe principal é complicada e você sente que precisa de objetos subordinados para gerenciar a classe
  3. Quando você percebe que o estado e a existência da classe dependem totalmente da classe envolvente

Costumo usar classes aninhadas para ocultar detalhes de implementação. Um exemplo da resposta de Eric Lippert aqui:

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

Esse padrão fica ainda melhor com o uso de genéricos. Ver essa questão para dois exemplos legais.Então acabo escrevendo

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

em vez de

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

Também posso ter uma lista genérica de Equality<Person> mas não EqualityComparer<Person, int>

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

enquanto

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

não é possível.Esse é o benefício da classe aninhada herdar da classe pai.

Outro caso (da mesma natureza - ocultando a implementação) é quando você deseja tornar os membros de uma classe (campos, propriedades etc.) acessíveis apenas para uma única 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
}

Como nawfal mencionada implementação do padrão Abstract Factory, esse código pode ser estendido para alcançar Padrão de clusters de classe que é baseado no padrão Abstract Factory.

Gosto de aninhar exceções exclusivas de uma única classe, ou seja.aqueles que nunca são jogados de qualquer outro lugar.

Por exemplo:

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

Isso ajuda a manter os arquivos do seu projeto organizados e não cheios de centenas de pequenas classes de exceção.

Lembre-se de que você precisará testar a classe aninhada.Se for privado, você não poderá testá-lo isoladamente.

Você poderia torná-lo interno, no entanto, Em conjunto com o InternalsVisibleTo atributo.Porém, isso seria o mesmo que tornar interno um campo privado apenas para fins de teste, o que considero uma autodocumentação ruim.

Portanto, você pode querer implementar apenas classes aninhadas privadas que envolvam baixa complexidade.

sim 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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top