web-dev-qa-db-fra.com

Pourquoi aurais-je besoin d'utiliser des classes imbriquées C #

J'essaie de comprendre les classes imbriquées en C #. Je comprends qu'une classe imbriquée est une classe définie dans une autre classe. Ce que je ne comprends pas, c'est pourquoi j'aurais besoin de le faire.

213
Dan Norton

Un motif qui me plaît particulièrement est de combiner des classes imbriquées avec le motif usine:

public abstract class BankAccount
{
  private BankAccount() {} // prevent third-party subclassing.
  private sealed class SavingsAccount : BankAccount { ... }
  private sealed class ChequingAccount : BankAccount { ... }
  public static BankAccount MakeSavingAccount() { ... }
  public static BankAccount MakeChequingAccount() { ... }
}

En imbriquant les classes de cette manière, il est impossible pour des tiers de créer leurs propres sous-classes. J'ai un contrôle complet sur tout le code qui s'exécute dans n'importe quel objet de compte bancaire. Et toutes mes sous-classes peuvent partager les détails de l'implémentation via la classe de base.

260
Eric Lippert

Le but généralement est simplement de restreindre le scope de la classe imbriquée. Les classes imbriquées comparées aux classes normales ont la possibilité supplémentaire du modificateur private (ainsi que protected bien sûr).

Fondamentalement, si vous devez uniquement utiliser cette classe à partir de la classe "parent" (en termes de portée), il est généralement approprié de la définir en tant que classe imbriquée. Si cette classe doit éventuellement être utilisée à partir de sans Assembly/library, il est généralement plus pratique pour l'utilisateur de la définir en tant que classe distincte (frère ou soeur), qu'il existe ou non une relation conceptuelle. entre les deux classes. Même s'il est techniquement possible de créer une classe public imbriquée dans une classe public parent, il s'agit rarement, à mon avis, d'une mise en œuvre appropriée.

111
Noldorin

Une classe imbriquée peut avoir private, protected et protected internal accès aux modificateurs avec public et internal.

Par exemple, vous implémentez la méthode GetEnumerator() qui renvoie un objet IEnumerator<T>. Les consommateurs ne se soucient pas du type réel de l'objet. Tout ce qu'ils savent, c'est qu'il implémente cette interface. La classe que vous voulez retourner n'a pas d'utilisation directe. Vous pouvez déclarer cette classe en tant que classe imbriquée private et en renvoyer une instance (c'est en fait ainsi que le compilateur C # implémente des itérateurs):

class MyUselessList : IEnumerable<int> {
    // ...
    private List<int> internalList;
    private class UselessListEnumerator : IEnumerator<int> {
        private MyUselessList obj;
        public UselessListEnumerator(MyUselessList o) {
           obj = o;
        }
        private int currentIndex = -1;
        public int Current {
           get { return obj.internalList[currentIndex]; }
        }
        public bool MoveNext() { 
           return ++currentIndex < obj.internalList.Count;
        }
    }
    public IEnumerator<int> GetEnumerator() {
        return new UselessListEnumerator(this);
    }
}
51
Mehrdad Afshari

ce que je ne comprends pas, c'est pourquoi j'aurais jamais besoin de le faire

Je pense que vous n'avez jamais besoin de . Étant donné une classe imbriquée comme celle-ci ...

class A
{
  //B is used to help implement A
  class B
  {
    ...etc...
  }
  ...etc...
}

... vous pouvez toujours déplacer la classe interne/imbriquée vers une portée globale, comme ceci ...

class A
{
  ...etc...
}

//B is used to help implement A
class B
{
  ...etc...
}

Cependant, lorsque B est uniquement utilisé pour aider à implémenter A, il est avantageux de faire de B une classe interne/imbriquée:

  • Il ne pollue pas la portée globale (par exemple, le code client qui peut voir A ne sait pas que la classe B existe même)
  • Les méthodes de B ont implicitement accès à des membres privés de A; considérant que si B n'était pas imbriqué dans A, B ne pourrait pas accéder aux membres de A à moins que ces membres soient internes ou publics; mais rendre ces membres internes ou publics les exposerait également à d'autres classes (pas seulement B); au lieu de cela, conservez ces méthodes de A private et laissez B y accéder en déclarant B en tant que classe imbriquée. Si vous connaissez le C++, cela revient à dire qu'en C #, toutes les classes imbriquées sont automatiquement un ' ami ' de la classe dans laquelle elles sont contenues (et , déclarer une classe imbriquée est le seul moyen de déclarer une amitié en C #, car celui-ci n’a pas de mot clé friend.

Quand je dis que B peut accéder aux membres privés de A, cela suppose que B se réfère à A; ce qui est souvent le cas, car les classes imbriquées sont souvent déclarées comme ceci ...

class A
{
  //used to help implement A
  class B
  {
    A m_a;
    internal B(A a) { m_a = a; }
    ...methods of B can access private members of the m_a instance...
  }
  ...etc...
}

... et construit à partir d'une méthode de A utilisant un code comme celui-ci ...

//create an instance of B, whose implementation can access members of self
B b = new B(this);

Vous pouvez voir un exemple dans la réponse de Mehrdad.

45
ChrisW

Il existe également de bonnes utilisations des membres imbriqués publics ...

Les classes imbriquées ont accès aux membres privés de la classe externe. Ainsi, un scénario dans lequel ce serait la bonne manière serait lors de la création d'une comparaison (c'est-à-dire l'implémentation de l'interface IComparer).

Dans cet exemple, FirstNameComparer a accès au membre private _firstName, ce qui ne serait pas le cas si la classe était une classe séparée ...

public class Person
{
    private string _firstName;
    private string _lastName;
    private DateTime _birthday;

    //...

    public class FirstNameComparer : IComparer<Person>
    {
        public int Compare(Person x, Person y)
        {
            return x._firstName.CompareTo(y._lastName);
        }
    }

}
30
Arjan Einbu

Il est parfois utile d'implémenter une interface qui sera renvoyée à l'intérieur de la classe, mais l'implémentation de cette interface doit être complètement cachée du monde extérieur.

Par exemple, avant l'ajout du rendement à C #, un moyen d'implémenter des énumérateurs était de placer l'implémentation de l'énumérateur en tant que classe privée dans une collection. Cela permettrait un accès facile aux membres de la collection, mais le monde extérieur n'aurait pas besoin de/voir les détails de la façon dont cela est mis en œuvre.

18
Reed Copsey

Les classes imbriquées sont très utiles pour implémenter des détails internes qui ne doivent pas être exposés. Si vous utilisez Reflector pour vérifier des classes telles que Dictionary <Tkey, TValue> ou Hashtable, vous en trouverez des exemples.

5
Fernando

Peut-être que ceci est un bon exemple de quand utiliser des classes imbriquées?

// ORIGINAL
class ImageCacheSettings { }
class ImageCacheEntry { }
class ImageCache
{
    ImageCacheSettings mSettings;
    List<ImageCacheEntry> mEntries;
}

Et:

// REFACTORED
class ImageCache
{
    Settings mSettings;
    List<Entry> mEntries;

    class Settings {}
    class Entry {}
}

PS: Je n'ai pas pris en compte quels modificateurs d'accès devraient être appliqués (privé, protégé, public, interne)

3
lambertwm