2009-04-29 54 views
26

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

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

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

Respuesta

23

Normalmente me reservo interiores-clases para uno de dos propósitos:

clases
  1. públicas que se derivan de su clase padre, donde la clase padre es una aplicación de base abstracta con uno o más métodos abstractos y cada subclase es una implementación que sirve a una implementación específica.después de leer diseño de la estructura y las Directrices veo que esto está marcado como "Evita", sin embargo lo uso en escenarios similares a las enumeraciones - althogh eso es probablemente dando una mala impresión, así

  2. Las clases internas son privados y son unidades de lógica comercial o, de otro modo, estrechamente vinculadas a su clase principal de una manera en que se rompen fundamentalmente cuando son consumidas o utilizadas por cualquier otra clase.

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

En proyectos grandes, se sorprendería de la frecuencia con la que puede encontrar inicialmente un componente fuertemente acoplado solo porque es el primero o principal lo hace parecer lógico, pero a menos que tenga una razón técnica o muy buena para bloquearlo abajo y esconderlo de la vista, entonces hay poco daño al exponer la clase para que otros componentes puedan consumirla.

Editar Tenga en cuenta que, aunque estamos hablando de subclases, deberían ser componentes más o menos bien diseñados y poco acoplados. Incluso si son privados e invisibles para el mundo exterior, mantener un "área superficial" mínima entre clases facilitará en gran medida el mantenimiento de su código para futuras expansiones o alteraciones.

+2

Esto: "fundamentalmente roto cuando es consumido o utilizado por cualquier otra clase". – Alonzzo2

9

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

Normalmente, cuando tienes una gran clase interna es porque la clase interna está estrechamente unida a su clase y necesita acceso a sus métodos privados.

8

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

Al hacer esto, puede obtener una visión general más amplia al editing the project file para hacer que los archivos se agrupen como las clases de diseñador en Windows Forms. Creo que he visto un complemento de Visual Studio que lo hace automágicamente, pero no recuerdo dónde.

EDIT:
Después de algún buscando encontré el Visual Studio Add-in para hacer esto llamados VSCommands

+2

qué no hacen esto de forma automática en Visual Studio si los nombra MiClase. cs, MyClass.subportion.cs, MyClass.anotherbit.cs, MyClass.lastoneipromise.cs, etc.? –

+0

No he hecho esto personalmente, pero quizás lo han implementado en Visual Studio 2008? – Patrik

+1

Acabo de probar, y Visual Studio no hace esto automáticamente. – Patrik

18

no tengo el libro a mano, pero las directrices de diseño de marco sugiere utilizar public clases internas siempre y cuando los clientes no tengan que referirse al nombre de la clase. private clases internas están bien: nadie va a notar esto.

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

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

En cuanto a su gran clase: es por lo general vale la pena dividir algo como esto en clases más pequeñas, cada una con una pieza específica de funcionalidad. Es difícil romperlo inicialmente, pero predigo que hará que su vida sea más fácil más adelante ...

+6

Algunos puntos del libro (pág. 102) ... los tipos anidados deben usarse con moderación; los tipos anidados son más adecuados para modelar instancias de implementación de sus tipos principales; el usuario final rara vez debe declarar un tipo anidado, y casi nunca crear una instancia; no use tipos anidados para agrupar - use espacios de nombres; EVITE TIPOS NESTED PUBLICOS – STW

+0

Muy claro y fácil de entender, gracias. – katmanco

3

Personalmente me gusta tener una clase por archivo y las clases internas como parte de ese archivo. Creo que las clases internas normalmente (casi siempre) deben 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 me funciona bien, en este caso, y evita que el archivo sea difícil de trabajar. Las regiones de código mantienen la clase interna "oculta", y dado que es un detalle de implementación privada, me parece bien.

2

Yo personalmente uso clases internas para encapsular algunos de los conceptos y operaciones que se usan internamente dentro de una clase. De esta forma, no contaminé la API no pública de esa clase y mantuve la API limpia y compacta.

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

6

Respecto única forma de estructurar una bestia ...

Puede utilizar las clases parciales para dividir la clase principal y las clases anidadas. Cuando lo haga, se le aconseja que nombre los archivos de manera apropiada, de modo que es obvio lo que está sucediendo.

// 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 se ven interfaces (explícitas) en su propio archivo. p.ej. Outer.ISomeInterface.cs en lugar del editor predeterminado de #region ing them.

Sus proyectos de estructura de archivos y luego empieza a parecerse

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

Normalmente, cuando estamos haciendo esto es por una variación del patrón del constructor.

0

En mi opinión, las clases internas, si es necesario, deben mantenerse pequeñas y ser utilizadas internamente solo por esa clase. Si usa Relfector en .NET Framework, 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 el mantenimiento.Tengo que apoyar algunos códigos existentes en los que alguien pensó que era una buena idea usar clases internas dentro de las clases internas. Resultó en una jerarquía de clase interna con niveles de 4 a 5 niveles. Huelga decir que el código es impenetrable y lleva años entender lo que estás mirando.

0

Aquí ver un ejemplo práctico de una clase anidada que podría darle una idea de su uso (añadido alguna prueba de la 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); 
} 
Cuestiones relacionadas