Pregunta

¿Cuáles son las mejores prácticas con respecto al uso y la estructura de las clases internas en C #?

Por ejemplo, si tengo una clase base muy grande y dos clases internas grandes, ¿debería dividirlos en archivos de código separados (clase parcial) o dejarlos como un archivo de código difícil de manejar muy grande?

¿También es una mala práctica tener una clase abstracta, con una clase interna pública heredada?

¿Fue útil?

Solución

Normalmente, reservo clases internas para uno de dos propósitos:

  1. Clases públicas que se derivan de su clase principal, donde la clase principal es una implementación base abstracta con uno o más métodos abstractos y cada subclase es una implementación que sirve para una implementación específica. después de leer el Diseño del marco y las Directrices, veo que esto está marcado como "Evitar", sin embargo, lo uso en escenarios similares a los enumerados, aunque probablemente eso también esté causando una mala impresión

  2. Las clases internas son privadas y son unidades de lógica de negocios o, de lo contrario, están estrechamente relacionadas con su clase principal de una manera en la que se rompen fundamentalmente cuando son consumidas o utilizadas por cualquier otra clase.

Para todos los demás casos, trato de mantenerlos en el mismo espacio de nombres y en el mismo nivel de accesibilidad que su consumidor / padre lógico, a menudo con nombres que son un poco menos amigables que el " main " clase.

En proyectos grandes, se sorprendería de la frecuencia con la que se encuentra inicialmente construyendo un componente fuertemente acoplado simplemente porque su primer o primer propósito lo hace parecer lógico, sin embargo, a menos que tenga una razón técnica o muy buena para bloquearlo Al ocultarlo de la vista, hay poco daño en exponer a la clase para que otros componentes puedan consumirla.

Editar Tenga en cuenta que aunque estamos hablando de subclases, deberían estar más o menos bien diseñados y tener componentes ligeramente acoplados. Incluso si son privados e invisibles para el mundo exterior, manteniendo una "área de superficie" mínima entre clases facilitará enormemente la capacidad de mantenimiento de su código para futuras ampliaciones o modificaciones.

Otros consejos

No tengo el libro a mano, pero las Pautas de diseño del marco sugieren el uso de clases internas public siempre que los clientes no tengan que referirse al nombre de la clase. Las clases internas private están bien: nadie las notará.

Malo: ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection ();

Bien: listView.Items.Add(...);

Con respecto a su clase grande: generalmente vale la pena dividir algo como esto en clases más pequeñas, cada una con una pieza específica de funcionalidad. Al principio es difícil dividirlo, pero predigo que te hará la vida más fácil más adelante ...

En general, las clases internas deben ser privadas y utilizables solo por la clase que las contiene. Si las clases internas son muy grandes, eso sugeriría que deberían ser sus propias clases.

Por lo general, cuando tienes una gran clase interna es porque la clase interna está estrechamente unida a la clase que contiene y necesita acceso a sus métodos privados.

Creo que esto es bastante subjetivo, pero probablemente los dividiría en archivos de código separados al hacer que " host " clase parcial.

Al hacer esto, puedes obtener aún más información general en editando el archivo de proyecto para haga los grupos de archivos al igual que las clases de diseñador en Windows Forms. Creo que he visto un complemento de Visual Studio que lo hace de manera automática, pero no recuerdo dónde.

EDITAR:
Después de buscar, encontré el complemento de Visual Studio para hacer esto llamado VSCommands

En cuanto a cómo estructurar una bestia ...

Puede usar clases parciales para dividir la clase principal y las clases anidadas. Cuando lo haga, se le recomienda que nombre los archivos adecuadamente para que sea obvio lo que está pasando.

// main class in file Outer.cs
namespace Demo
{
  public partial class Outer
  {
     // Outer class
  }
}

// nested class in file Outer.Nested1.cs
namespace Demo
{
  public partial class Outer
  {
    private class Nested1
    {
      // Nested1 details
    }
  }
}

De la misma manera, a menudo ves interfaces (explícitas) en su propio archivo. p.ej. Outer.ISomeInterface.cs en lugar del editor predeterminado de #region ing.

La estructura de archivos de tus proyectos luego comienza a parecerse

   /Project/Demo/ISomeInterface.cs
   /Project/Demo/Outer.cs
   /Project/Demo/Outer.Nested1.cs
   /Project/Demo/Outer.ISomeInterface.cs

Por lo general, cuando hacemos esto es por una variación del patrón del Generador.

Personalmente, me gusta tener una clase por archivo y las clases internas como parte de ese archivo. Creo que las clases internas normalmente deberían (casi siempre) ser privadas, y son un detalle de implementación de la clase. Tenerlos en un archivo separado confunde las cosas, IMO.

El uso de regiones de código para envolver las clases internas y ocultar sus detalles funciona bien para mí, en este caso, y evita que sea difícil trabajar con el archivo. Las regiones de código mantienen la clase interna "oculta", y como es un detalle de implementación privada, eso está bien para mí.

Personalmente uso clases internas para resumir algunos de los conceptos y operaciones que solo se utilizan internamente dentro de una clase. De esta manera no contamino la api no pública de esa clase y mantengo la api limpia y compacta.

Puede aprovechar las clases parciales para mover la definición de estas clases internas a un archivo diferente para una mejor organización. VS no agrupa automáticamente los archivos de clase parciales a excepción de algunos de los elementos templados, como ASP.NET, formularios de WinForm, etc. Deberá editar el archivo de proyecto y hacer algunos cambios allí. Puede ver uno de los grupos existentes allí para ver cómo se hace. Creo que hay algunas macros que te permiten agrupar archivos de clase parciales en el explorador de soluciones.

En mi opinión, las clases internas, si son necesarias, deberían mantenerse pequeñas y ser utilizadas internamente solo por esa clase. Si usa Relfector en el marco .NET, verá que se usan mucho solo para ese propósito.

Si sus clases internas se están volviendo demasiado grandes, definitivamente las movería a clases / archivos de código separados de alguna manera, aunque solo sea por mantenimiento. Tengo que admitir un código existente en el que alguien pensó que era una gran idea usar clases internas dentro de clases internas. Resultó en una jerarquía de clases interna que se ejecutaba de 4 a 5 niveles de profundidad. No hace falta decir que el código es impenetrable y lleva mucho tiempo entender lo que está viendo.

Aquí puedes ver un ejemplo práctico de una clase anidada que podría darte una idea de su uso (se agregó alguna prueba de unidad)

namespace CoreLib.Helpers
{
    using System;
    using System.Security.Cryptography;

    public static class Rnd
    {
        private static readonly Random _random = new Random();

        public static Random Generator { get { return _random; } }

        static Rnd()
        {
        }

        public static class Crypto
        {
            private static readonly RandomNumberGenerator _highRandom = RandomNumberGenerator.Create();

            public static RandomNumberGenerator Generator { get { return _highRandom; } }

            static Crypto()
            {
            }

        }

        public static UInt32 Next(this RandomNumberGenerator value)
        {
            var bytes = new byte[4];
            value.GetBytes(bytes);

            return BitConverter.ToUInt32(bytes, 0);
        }
    }
}

[TestMethod]
public void Rnd_OnGenerator_UniqueRandomSequence()
{
    var rdn1 = Rnd.Generator;
    var rdn2 = Rnd.Generator;
    var list = new List<Int32>();
    var tasks = new Task[10];
    for (var i = 0; i < 10; i++)
    {
        tasks[i] = Task.Factory.StartNew((() =>
        {
            for (var k = 0; k < 1000; k++)
            {
                lock (list)
                {
                    list.Add(Rnd.Generator.Next(Int32.MinValue, Int32.MaxValue));
                }
            }
        }));
    }
    Task.WaitAll(tasks);
    var distinct = list.Distinct().ToList();
    Assert.AreSame(rdn1, rdn2);
    Assert.AreEqual(10000, list.Count);
    Assert.AreEqual(list.Count, distinct.Count);
}

[TestMethod]
public void Rnd_OnCryptoGenerator_UniqueRandomSequence()
{
    var rdn1 = Rnd.Crypto.Generator;
    var rdn2 = Rnd.Crypto.Generator;
    var list = new ConcurrentQueue<UInt32>();
    var tasks = new Task[10];
    for (var i = 0; i < 10; i++)
    {
        tasks[i] = Task.Factory.StartNew((() =>
        {
            for (var k = 0; k < 1000; k++)
            {
                    list.Enqueue(Rnd.Crypto.Generator.Next());
            }
        }));
    }
    Task.WaitAll(tasks);
    var distinct = list.Distinct().ToList();
    Assert.AreSame(rdn1, rdn2);
    Assert.AreEqual(10000, list.Count);
    Assert.AreEqual(list.Count, distinct.Count);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top