Domanda

Quali sono le migliori pratiche relative all'uso e alla struttura delle classi interne in C #.

Ad esempio se ho una classe base molto grande e due grandi classi interne dovrei dividerli in file di codice separati (classe parziale) o lasciarli come un file di codice ingombrante molto grande?

È anche una cattiva pratica avere una classe astratta, con una classe interna ereditata dal pubblico?

È stato utile?

Soluzione

In genere riservo classi interne per uno dei due scopi:

  1. Classi pubbliche che derivano dalla loro classe genitore in cui la classe genitore è un'implementazione di base astratta con uno o più metodi astratti e ogni sottoclasse è un'implementazione che serve un'implementazione specifica. dopo aver letto Framework Design and Guidelines, vedo che questo è contrassegnato come " Avoid " ;, tuttavia lo uso in scenari simili a enum - althogh che probabilmente sta dando una brutta impressione anche

  2. Le classi interne sono private e sono unità di business logic o altrimenti strettamente legate alla loro classe genitore in un modo in cui sono fondamentalmente rotte quando consumate o utilizzate da qualsiasi altra classe.

Per tutti gli altri casi, provo a mantenerli nello stesso spazio dei nomi e nello stesso livello di accessibilità del loro consumatore / genitore logico, spesso con nomi un po 'meno amichevoli del "principale". Classe.

Sui grandi progetti ti sorprenderai quanto spesso potresti trovarti inizialmente a costruire un componente fortemente accoppiato solo perché il suo scopo principale o primario lo fa sembrare logico, tuttavia a meno che tu non abbia una ragione molto buona o tecnica per bloccarlo giù e nasconderlo alla vista, quindi c'è poco danno nell'esporre la classe in modo che altri componenti possano consumarla.

Modifica Tieni presente che anche se stiamo parlando di sottoclassi dovrebbero essere componenti più o meno ben progettati e liberamente accoppiati. Anche se sono privati ??e invisibili al mondo esterno mantenendo una "superficie" minima ". tra le classi faciliterà notevolmente la manutenibilità del codice per future espansioni o alterazioni.

Altri suggerimenti

Non ho il libro a portata di mano, ma le Linee guida per la progettazione del framework suggeriscono di usare le classi interne public purché i clienti non debbano fare riferimento al nome della classe. Le classi interne private vanno bene: nessuno le noterà.

Bad: Collezione ListView.ListViewItemCollection = new ListView.ListViewItemCollection ();

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

Per quanto riguarda la tua grande classe: in genere vale la pena dividere qualcosa del genere in classi più piccole, ognuna con una specifica funzionalità. Inizialmente è difficile romperlo, ma prevedo che ti semplificherà la vita in seguito ...

Le classi interne in genere dovrebbero essere private e utilizzabili solo dalla classe che le contiene. Se le loro classi interne sono molto grandi, ciò suggerirebbe che dovrebbero essere le loro classi.

Di solito quando hai una grande classe interna è perché la classe interna è strettamente accoppiata alla sua classe contenente e ha bisogno di accedere ai suoi metodi privati.

Penso che questo sia piuttosto soggettivo, ma probabilmente li dividerei in file di codice separati facendo l'host "quot" classe parziale.

In questo modo, puoi ottenere una panoramica ancora maggiore modificando il file di progetto in rendere il gruppo di file proprio come le classi di progettazione in Windows Form. Penso di aver visto un componente aggiuntivo di Visual Studio che lo fa automaticamente per te, ma non ricordo dove.

Modifica
Dopo un po 'di ricerca ho trovato il componente aggiuntivo di Visual Studio per fare questo chiamato VSCommands

Riguardo solo a come strutturare una tale bestia ...

Puoi usare le classi parziali per dividere la classe principale e le classi nidificate. Quando lo fai, ti viene consigliato di nominare i file in modo appropriato, quindi è ovvio cosa sta succedendo.

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

Allo stesso modo, spesso vedete interfacce (esplicite) nel loro file. per esempio. Outer.ISomeInterface.cs anziché l'impostazione predefinita dell'editor di #region in uso.

La struttura del file dei progetti inizia quindi ad apparire

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

In genere quando lo facciamo è per una variazione del modello Builder.

Personalmente mi piace avere una classe per file e le classi interne come parte di quel file. Credo che le classi interne dovrebbero in genere (quasi sempre) essere private e sono un dettaglio di implementazione della classe. Il fatto di averli in un file separato confonde le cose, IMO.

L'uso delle aree di codice per avvolgere le classi interne e nascondere i loro dettagli funziona bene per me, in questo caso, e impedisce al file di lavorare con difficoltà. Le aree di codice mantengono la classe interna "nascosta" e, poiché si tratta di un dettaglio dell'implementazione privata, va bene per me.

Uso personalmente le classi interne per incapsulare alcuni concetti e operazioni usati solo internamente all'interno di una classe. In questo modo non inquino l'API non pubblico di quella classe e mantengo l'API pulito e compatto.

Puoi sfruttare le classi parziali per spostare la definizione di queste classi interne in un file diverso per una migliore organizzazione. VS non raggruppa automaticamente i file di classe parziali per te ad eccezione di alcuni degli elementi modellati come ASP.NET, moduli WinForm, ecc. Dovrai modificare il file di progetto e apportare alcune modifiche. Puoi guardare uno dei raggruppamenti esistenti lì dentro per vedere come è fatto. Credo che ci siano alcune macro che ti consentono di raggruppare file di classi parziali per te in Esplora soluzioni.

Secondo me le classi interne, se necessario, dovrebbero essere tenute piccole e utilizzate internamente solo da quella classe. Se usi Relfector sul framework .NET, li vedrai molto usati solo per quello scopo.

Se le tue classi interne stanno diventando troppo grandi, le sposterei sicuramente in classi / file di codice separati in qualche modo, se non altro per motivi di manutenzione. Devo supportare un codice esistente in cui qualcuno ha pensato che fosse una grande idea usare le classi interne all'interno delle classi interne. Il risultato è una gerarchia di classi interne con profondità di 4 - 5 livelli. Inutile dire che il codice è impenetrabile e richiede anni per capire cosa stai guardando.

Qui vedi un esempio pratico di una classe nidificata che potrebbe darti un'idea del loro uso (aggiunto un test unitario)

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