web-dev-qa-db-fra.com

Utiliser les classes intérieures en C #

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 séparés (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 du public?

26
DevinB

En général, je réserve des classes intérieures pour l'une des deux raisons suivantes:

  1. Les classes publiques qui dérivent de leur classe parent, 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 qui sert une implémentation spécifique. après avoir lu Framework Design and Guidelines, je vois que cela est marqué "Eviter", mais je l'utilise dans des scénarios similaires à ceux d'énums - althogh qui donnent probablement une mauvaise impression également

  2. Les classes internes sont des unités privées et sont des unités de logique métier ou sont étroitement associées à leur classe mère de manière à ce qu’elles soient fondamentalement endommagées lorsqu’elles sont consommées ou utilisées par une 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 la classe "principale".

Sur de gros projets, vous seriez surpris de voir combien de fois vous construirez initialement un composant fortement couplé simplement parce que son objectif premier ou principal le rend logique, mais à moins que vous n'ayez une très bonne raison technique de le verrouiller de la vue, il y a peu de mal à exposer la classe de sorte que d’autres composants puissent la consommer.

Edit N'oubliez pas que même si nous parlons de sous-classes, elles devraient ê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, le fait de conserver une "surface" minimale entre les classes facilitera grandement la maintenance de votre code en vue d'une extension ou d'une modification ultérieure.

23
STW

Je n'ai pas le livre sous la main, mais les directives de conception de structure suggèrent d'utiliser public des classes internes, à condition que les clients n'aient pas à se référer au nom de la classe. private Les classes intérieures vont bien: 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. C’est difficile de rompre au début, mais je prédis que cela vous facilitera la vie plus tard ...

18
Tim Robinson

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. 

Généralement, 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. 

9
Paul Alexander

Je pense que cela est plutôt subjectif, mais je les scinderais probablement en fichiers de code séparés en rendant la classe "Host" partielle.

En procédant ainsi, vous pouvez obtenir encore plus de vue d'ensemble en modifiant le fichier de projet pour que les fichiers soient regroupés 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.

8
Patrik Svensson

En ce qui concerne seulement comment structurer une telle bête ...

Vous pouvez utiliser des classes partielles pour fractionner 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 manière à ce que ce qui se passe soit é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.

Votre structure de fichier de projets commence alors à ressembler à

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

Généralement, lorsque nous procédons ainsi, il s’agit d’une variante du motif Builder.

6
Robert Paulson

Personnellement, j'aime bien avoir une classe par fichier et les classes internes dans 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é confond les choses, IMO.

Utiliser des régions de code pour envelopper 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 "cachée", et comme il s'agit d'un détail d'implémentation privée, ça me convient.

3
Reed Copsey

Personnellement, j'utilise 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 crois que certaines macros vous permettent de regrouper des fichiers de classe partiels dans l'explorateur de solutions.

2
Mehmet Aras

À 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 uniquement à cette fin.

Si vos classes internes deviennent trop grandes, 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.

0
Peter Monks

Voici 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);
}
0
alhpe