Novembre 2016

Volume 31, numéro 11

Cet article a fait l'objet d'une traduction automatique.

.NET Framework : ressources disponibles cachées

Par Artak Mkrtchyan | Novembre 2016

Types jetables sont idéales, car ils vous permettent de libérer des ressources de façon déterministe. Toutefois, il existe des situations où les développeurs travaillent avec des types jetables sans même vous en rendre compte. L’utilisation de modèles de conception creational est un exemple d’une situation où l’utilisation d’un type jetable peut ne pas être évidente, ce qui peut entraîner un objet non supprimé la mise en route. Cet article explique comment résoudre le problème. Je vais commencer par passer en revue les modèles de design creational.

Modèles de conception creational

Un énorme avantage les modèles de design creational est l’abstraction les implémentations réelles et de « communiquer » dans la langue de l’interface. Ils traitent des mécanismes de création d’objet pour créer des objets qui sont adaptés à la solution. Par rapport à la création d’objets de base, les modèles de conception creational améliorent plusieurs aspects du processus de création d’objet. Voici deux avantages bien connus des modèles de conception creational :

  • Abstraction : Ils le type d’objet en cours de création, donc l’appelant ne sait pas que l’objet réel qui est retournée est abstrait, qu’elles soient conscientes de l’interface.
  • Éléments internes de création : Ils encapsulent la base de connaissances sur la création d’instance de type spécifique.

Ensuite, je vais donner une brève présentation des deux modèles de conception creational bien connu.

Le modèle de Design Factory méthodemodèle de conception de la méthode de fabrique est un de Mes favoris et utiliser beaucoup dans mon travail quotidien. Ce modèle utilise des méthodes de fabrique pour traiter le problème de création d’objets sans spécification de la classe exacte de l’objet qui est créé. Au lieu d’appeler directement un constructeur de classe, vous appelez une méthode de fabrique pour créer l’objet. La méthode de fabrique renvoie une abstraction (interface ou une classe de base), les enfants des classes implémentent. Figure 1 présente le diagramme de langage UML (Unified Modeling) pour ce modèle.

Dans Figure 1, le ConcreteProduct est un type spécifique de l’abstraction/interface IProduct. De même, le ConcreteCreator est une implémentation spécifique de l’interface ICreator.

Modèle de Design Factory (méthode)
Figure 1 modèle de Design Factory (méthode)

Le client de ce modèle utilise une instance de ICreator et allez appeler sa méthode Create pour obtenir une nouvelle instance de IProduct, sans savoir quel produit réel a été retourné.

Le modèle de Design Factory abstraite l’objectif du modèle de design Factory abstraite est de fournir une interface pour créer des familles d’objets connexes ou dépendants sans spécifier d’implémentations concrètes.

Cela shelters le code client à partir de création de l’objet du boîtier en sorte que le client demande l’objet de fabrique pour créer un objet du type abstrait souhaité et retourner un pointeur abstrait à l’objet vers le client. En particulier, cela signifie que le code client ne connaît pas sur le type concret. Il porte uniquement sur un type abstrait.

Ajout de prise en charge de nouveaux types concrets est géré en créant de nouveaux types de fabrique et en modifiant le code client pour utiliser un type de fabrique différents en fonction des besoins. Dans la plupart des cas, il s’agit d’une modification du code d’une ligne. Évidemment, cela simplifie la gestion des modifications, comme le code client n’a pas besoin de modifier pour prendre en charge le nouveau type de fabrique. Figure 2 montre le diagramme UML pour le modèle de design Factory abstraite.

Modèle de Design Factory abstraite
Modèle de Design Factory abstraite figure 2

Du point de vue du client, l’utilisation de la fabrique abstraite est représentée par le fragment de code suivant :

IAbstractFactory factory = new ConcreteFactory1();
IProductA product = factory.CreateProductA();

Le client est libre de modifier l’implémentation réelle de fabrique pour contrôler le type de produit créée en arrière-plan et qui n’a absolument aucun impact sur le code.

Ce code est juste un exemple ; dans le code correctement structuré, l’instanciation de fabrique lui-même aurait probablement être abstrait, avec un modèle de méthode de fabrique par exemple.

Le problème

Dans les deux exemples de modèle de conception, une fabrique était impliqué. Une fabrique est la méthode/procédure réel, qui retourne une référence de type construit via une abstraction en réponse à l’appel du client.

Techniquement, vous pouvez utiliser une fabrique pour créer un objet, partout où il existe une abstraction, comme indiqué dans Figure 3.

Exemple d’Abstraction Simple et son utilisation
Figure 3 exemple d’Abstraction Simple et son utilisation

La fabrique gère le choix entre différentes implémentations disponibles, selon les facteurs impliqués.

Conformément au principe d’Inversion de dépendance :

  • Modules de haut niveau ne doivent pas dépendre de modules de bas niveau. Tous deux doivent dépendre des abstractions.
  • Les abstractions ne doivent pas dépendre de détails. Détails doivent dépendre des abstractions.

Techniquement parlant, ainsi que sur tous les niveaux d’une chaîne de dépendance, la dépendance doit être remplacée par une abstraction. En outre, la création de ces abstractions peut et dans de nombreux cas doit être : gérées via des fabriques.

Tout cela met l’accent sur l’importance fabriques sont quotidiennes de codage. Toutefois, ils sont en fait de masquer le problème : types jetables. Avant d’entrer dans les détails, je parlerai tout d’abord l’interface IDisposable et le modèle de Design Dispose.

Supprimer le modèle de conception

Tous les programmes d’acquérir des ressources telles que la mémoire, des handles de fichiers et des connexions de base de données pendant leur exécution. Les développeurs doivent être prudent lors de l’utilisation de ces ressources, car les ressources doivent être libérées après avoir été acquis et utilisés.

Le Common Language Runtime (CLR) prend en charge pour la gestion automatique de la mémoire par le garbage collector (GC). Vous n’êtes pas obligé de nettoyer la mémoire managée explicitement, car le garbage collector effectue cette opération automatiquement. Malheureusement, il existe d’autres types de ressources (tels que des ressources non managées) qui doivent encore être libérées de manière explicite. Le catalogue global n’est pas conçu pour gérer ces types de ressources, il est la responsabilité du développeur pour libérer les.

Cependant, le CLR permet aux développeurs de traiter les ressources non managées. Le type System.Object définit une méthode virtuelle publique, appelée Finalize, qui est appelée par le garbage collector avant que la mémoire de l’objet est récupérée. La méthode Finalize généralement est appelée finaliseur. Vous pouvez substituer la méthode pour nettoyer les ressources non managées supplémentaires utilisées par l’objet.

Ce mécanisme, cependant, présente quelques inconvénients en raison de certains aspects de l’exécution du garbage collector.

Le finaliseur est appelé lorsque le garbage collector détecte qu’un objet est éligible pour la collection. Cela se produit sur une période indéterminée, une fois que l’objet n’est plus nécessaire.

Lorsque le garbage collector doit appeler le finaliseur, il doit reporter la prochaine série de garbage collection à la collection réelle de la mémoire. Cela diffère de la collection l’objet mémoire voire plue. C’est là qu’intervient l’interface System.IDisposable. Microsoft .NET Framework fournit l’interface IDisposable que vous devez implémenter pour fournir aux développeurs un mécanisme pour libérer manuellement les ressources non managées. Types qui implémentent cette interface sont appelés types jetables. L’interface IDisposable définit qu’une méthode sans paramètre appelée Dispose. Dispose doit être appelée pour libérer immédiatement des ressources non managées qu’elle référence dès que l’objet n’est pas nécessaire.

Vous pouvez demander « Pourquoi dois-je appeler Dispose moi-même lorsque je sais que GC finalement gérera il me ? » La réponse nécessite un article distinct, ce qui concerne également sur les aspects de l’impact de l’exécution le GC sur les performances. Cela est abordée dans cet article, je vais passer.

Il existe des règles à suivre pour déterminer si un type doit être supprimable. La règle générale est la suivante : Si un objet d’un type donné doit faire référence à une ressource non managée ou autres objets jetables, il doit également être disponible.

Le modèle de suppression définit une implémentation spécifique de l’interface IDisposable. Il nécessite deux méthodes Dispose à mettre en oeuvre : une publique sans paramètres (définis par l’interface IDisposable) et l’autre un protégé virtuel avec un paramètre booléen. Évidemment, si le type doit être scellé, protégé virtuel doit être remplacé en privé.

Implémentation de la figure 4 du modèle de conception Dispose

public class DisposableType : IDisposable {
  ~DisposableType() {
    this.Dispose(false);
  }
  public void Dispose() {
    this.Dispose(true);
    GC.SuppressFinalize(this);
  }
  protected virtual void Dispose(bool disposing) {
    if (disposing) {
      // Dispose of all the managed resources here
    }
    // Dispose of all the unmanaged resources here
  }
}

Le paramètre booléen indique la manière dans laquelle la méthode dispose est appelée. La méthode publique appelle donc le protégé avec un paramètre de valeur « true ». De même, les surcharges de la méthode Dispose (bool) dans la hiérarchie de classe doivent appeler base. Dispose (true).

L’implémentation du modèle de suppression nécessite également la méthode Finalize surchargée. Cela pour couvre les scénarios où un développeur oublie d’appeler la méthode Dispose, une fois que l’objet n’est plus nécessaire. Étant donné que le finaliseur est appelé par le garbage collector, les ressources managées référencés peut déjà (ou sera) nettoyés, afin que vous devez gérer la libération des ressources non managées uniquement lorsque la méthode Dispose (bool) est appelée à partir du finaliseur.

Passage du sujet principal, le problème qu’intervient lorsque vous utilisez des objets jetables lorsqu’il est utilisé avec les modèles de conception creational.

Imaginez un scénario où un des types concrets implémentant l’abstraction également implémente l’interface IDisposable. Supposons qu’il s’agit du ConcreteImplementation2 dans mon exemple, comme dans Figure 5.

Abstraction avec une implémentation IDisposable
Abstraction de la figure 5 avec une implémentation IDisposable

Notez que l’interface IAbstraction n’hérite pas de l’interface IDisposable.

À présent, examinez le code client, où l’abstraction doit être utilisé. L’interface IAbstraction n’a pas changé, le client ne charge même sur les modifications potentielles dans les coulisses. Naturellement, le client ne suppose qu’il a reçu un objet, dont il est responsable de la suppression. La réalité est qu’une instance IDisposable n’est pas véritablement attendue il et dans de nombreux cas, ces objets ne sont supprimées explicitement par le code client.

L’espoir est que l’implémentation réelle de le ConcreateImplementation2 implémente le modèle de Design Dispose, ce qui n’est pas toujours le cas.

Maintenant, il est évident que le mécanisme le plus simple pour gérer un cas où l’instance IAbstraction retournée implémente également l’interface IDisposable consisterait à introduire une vérification explicite dans le code client, comme illustré ici :

IAbstraction abstraction = factory.Create();
try {
  // Operations with abstraction go here
}
finally {
  if (abstraction is IDisposable)
    (abstraction as IDisposable).Dispose();
}

Ceci, cependant, bientôt devient une procédure fastidieuse.

Malheureusement, un à l’aide ne peut pas être utilisé avec IAbstraciton, car il n’étend pas explicitement IDisposable. Donc j’en suis arrivé avec une classe d’assistance qui encapsule la logique dans le bloc finally et vous permet de vous utilisez le bloc ainsi. Figure 6 illustre le code complet de la classe et fournit également un exemple d’utilisation.

Figure 6 PotentialDisposable Type et son utilisation

public sealed class PotentialDisposable<T> : IDisposable where T : class {
  private readonly T instance;
  public T Instance { get { return this.instance; } }
  public PotentialDisposable(T instance) {
    if (instance == null) {
      throw new ArgumentNullException("instance");
    }
    this.instance = instance;
  }
  public void Dispose() {
    IDisposable disposableInstance = this.Instance as IDisposable;
    if (disposableInstance != null) {
      disposableInstance.Dispose();
    }
  }
}
The client code:
IAbstraction abstraction = factory.Create();
using (PotentialDisposable<IAbstraction> wrappedInstance =
  new PotentialDisposable<IAbstraction>(abstraction)) {
    // Operations with abstraction wrapedInstance.Instance go here
}

Comme vous pouvez le voir dans la partie « Le code client » de Figure 6, à l’aide de la classe PotentialDisposable < T > réduit le code client à quelques lignes avec une à l’aide de bloc.

Vous pourriez dire que vous pouvez simplement mettre à jour l’interface IAbstraction et rendre IDisposable. Cela peut être la meilleure solution dans certains cas, mais pas d’autres.

Dans une situation où vous possédez l’interface IAbstraction et il est judicieux de IAbstraction étendre IDisposable, vous devez le faire. En fait, un bon exemple est la classe abstraite System.IO.Stream. La classe implémente l’interface IDisposable, mais il n’a aucune raison véritable défini. La raison est que les auteurs de la classe savait que la plupart des classes enfants auront un type de membres pouvant être supprimés.

Une autre situation : Lorsque vous ne possédez pas l’interface IAbstraction, mais il n’y a pour pouvoir étendre IDisposable, comme la plupart des implémentations de celui-ci ne sont pas jetables. Par exemple, pensez à une interface ICustomCollection. Vous avez plusieurs implémentations en mémoire et soudain vous devez ajouter une implémentation de base de données sauvegardée, qui sera l’implémentation jetable uniquement.

La situation serait lorsque vous ne possédez pas IAbstraction interface, ce qui évite de contrôle. Prenons un exemple de ICollection, qui est soutenu par une base de données.

Pour résumer

L’abstraction que vous obtenez est via une méthode de fabrique, ou non, il est important de conserver des produits à usage éphémère à l’esprit lorsque vous écrivez votre code client. À l’aide de cette classe d’assistance simple est une façon de garantir que votre code est aussi efficace que possible lorsque vous traitez des objets jetables.


Artak Mkrtchyan est ingénieur logiciel senior vivre à Redmond, Wash.  Il adore comme il adore pêche de codage.

Merci à l'expert technique Microsoft suivant d'avoir relu cet article : Paul Brambilla
Paul Brambilla est un développeur de logiciels senior chez Microsoft, spécialisé dans les services de cloud computing et d’infrastructure fondamentaux.