Question

Quelles sont les meilleures pratiques concernant l'utilisation et la structure des classes internes en C #.

Par exemple, si j’ai une très grande classe de base et deux grandes classes internes, dois-je les diviser en fichiers de code distincts (classe partielle) ou les laisser comme un très grand fichier de code lourd?

Est-ce également une mauvaise pratique d'avoir une classe abstraite, avec une classe interne héritée publique?

Était-ce utile?

La solution

En règle générale, je réserve des classes internes à l'une des deux fins suivantes:

  1. Les classes publiques qui dérivent de leur classe mère où la classe mère est une implémentation de base abstraite avec une ou plusieurs méthodes abstraites et chaque sous-classe est une implémentation servant une implémentation spécifique. après avoir lu Framework Design and Guidelines, je vois que cela est marqué comme "Éviter", mais je l'utilise dans des scénarios similaires à ceux d'énumération - mais cela donne probablement une mauvaise impression aussi.

  2. Les classes internes sont privées et constituent des unités de logique métier ou sont étroitement couplées à leur classe mère de manière à être fondamentalement endommagées lorsqu'elles sont consommées ou utilisées par toute autre classe.

Dans tous les autres cas, j’essaie de les conserver dans le même espace de noms et le même niveau d’accessibilité que leur consommateur / parent logique, souvent avec des noms un peu moins conviviaux que le "principal". classe.

Sur de gros projets, vous seriez surpris de voir combien de fois vous construisez initialement un composant à couplage fort simplement parce que c'est son objectif premier ou principal qui le rend logique, mais à moins que vous n'ayez une très bonne ou technique raison de le verrouiller masquer à la vue, il y a peu de mal à exposer la classe afin que les autres composants puissent la consommer.

Modifier N'oubliez pas que même si nous parlons de sous-classes, elles doivent être plus ou moins bien conçues et couplées de manière approximative. Même s’ils sont privés et invisibles au monde extérieur, ils conservent une "surface" minimale. entre les classes facilitera grandement la maintenabilité de votre code pour une extension ou une modification future.

Autres conseils

Je n'ai pas le livre sous la main, mais les directives de conception du framework suggèrent d'utiliser des classes internes public tant que les clients ne doivent pas se référer au nom de la classe. Les classes internes private sont acceptables: personne ne les remarquera.

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

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

En ce qui concerne votre grande classe: il est généralement intéressant de scinder quelque chose de ce genre en petites classes, chacune avec une fonctionnalité spécifique. Au début, c’est difficile de le casser, mais je prédis que cela vous facilitera la vie plus tard ...

Généralement, les classes internes doivent être privées et utilisables uniquement par la classe qui les contient. Si les classes internes sont très grandes, cela suggérerait qu'elles devraient être leurs propres classes.

Habituellement, lorsque vous avez une grande classe interne, c'est parce que la classe interne est étroitement liée à la classe qui la contient et nécessite un accès à ses méthodes privées.

Je pense que cela est plutôt subjectif, mais je les scinderais probablement dans des fichiers de code distincts en rendant le "hôte". classe partielle.

En procédant ainsi, vous pouvez obtenir une vue d'ensemble encore plus complète en en modifiant le fichier de projet pour faites le groupe de fichiers comme des classes de concepteur dans Windows Forms Je pense avoir vu un complément de Visual Studio faire cela automatiquement pour vous, mais je ne me souviens plus où.

MODIFIER:
Après quelques recherches, j'ai trouvé le complément Visual Studio appelé VSCommands

.

Concernant seulement comment structurer une telle bête ...

Vous pouvez utiliser des classes partielles pour scinder la classe principale et les classes imbriquées. Lorsque vous le faites, il vous est conseillé de nommer les fichiers de manière appropriée, de sorte que ce qui se passe est évident.

// 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 même manière, vous voyez souvent des interfaces (explicites) dans leur propre fichier. par exemple. Outer.ISomeInterface.cs plutôt que l'éditeur par défaut de #region les ing.

La structure de votre fichier de projets commence alors à ressembler à

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

Typiquement, lorsque nous faisons cela, c'est pour une variante du motif Builder.

Personnellement, j’aime bien avoir une classe par fichier et les classes internes comme faisant partie de ce fichier. Je crois que les classes internes doivent généralement (presque toujours) être privées et constituent un détail de mise en œuvre de la classe. Les avoir dans un fichier séparé est source de confusion, IMO.

Utiliser des régions de code pour entourer les classes internes et masquer leurs détails me convient parfaitement, dans ce cas, et évite que le fichier ne soit difficile à manipuler. Les régions de code gardent la classe interne "masquée", et comme il s'agit d'un détail d'implémentation privée, ça me convient.

J'utilise personnellement des classes internes pour encapsuler certains des concepts et opérations utilisés uniquement en interne au sein d'une classe. De cette façon, je ne pollue pas les API non publiques de cette classe et ne les maintiens pas propres et compactes.

Vous pouvez tirer parti des classes partielles pour déplacer la définition de ces classes internes dans un fichier différent pour une meilleure organisation. VS ne regroupe pas automatiquement les fichiers de classe partiels pour vous, à l'exception de certains éléments tels que ASP.NET, les formulaires WinForm, etc. Vous pouvez consulter l'un des groupes existants pour voir comment cela se fait. Je pense qu’il existe des macros qui vous permettent de regrouper des fichiers de classe partiels dans l’explorateur de solutions.

À mon avis, les classes internes, si nécessaire, devraient être réduites et utilisées en interne par cette classe uniquement. Si vous utilisez Relfector sur le framework .NET, vous verrez qu’ils sont beaucoup utilisés à cette fin.

Si vos classes internes deviennent trop volumineuses, je les déplacerais certainement dans des classes / fichiers de code distincts, ne serait-ce que pour la maintenabilité. Je dois supporter un code existant dans lequel quelqu'un pensait que c'était une bonne idée d'utiliser des classes internes dans des classes internes. Il en a résulté une hiérarchie de classe interne de 4 à 5 niveaux. Inutile de dire que le code est impénétrable et prend des siècles pour comprendre ce que vous regardez.

Vous voyez ici un exemple pratique de classe imbriquée qui pourrait vous donner une idée de leur utilisation (ajout d'un test unitaire)

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);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top