169 votes

Bonne ou mauvaise pratique? Initialisation d'objets dans getter

J'ai une étrange habitude qu'il semble... selon mon collègue au moins. Nous avons travaillé sur un petit projet ensemble. La façon dont j'ai écrit les classes (exemple simplifié):

[Serializable()]
public class Foo
{
    public Foo()
    { }

    private Bar _bar;

    public Bar Bar
    {
        get
        {
            if (_bar == null)
                _bar = new Bar();

            return _bar;
        }
        set { _bar = value; }
    }
}

Donc, fondamentalement, je ne initialiser un champ lorsqu'un getter est appelé et le champ est toujours null. J'ai pensé que cela permettrait de réduire la surcharge par le pas de l'initialisation de toutes les propriétés qui ne sont pas utilisées n'importe où.

ETA: La raison pour laquelle je n'ai c'est que ma classe possède plusieurs propriétés qui renvoie une instance d'une autre classe, qui à leur tour ont également des propriétés avec encore plus de classes, et ainsi de suite. Appeler le constructeur de la classe supérieure qui allait par la suite appeler tous les constructeurs pour toutes ces catégories, quand ils ne sont pas toujours nécessaires.

Existe-il des objections à l'encontre de cette pratique, d'autres que la préférence personnelle?

Mise à JOUR: j'ai examiné les nombreuses opinions divergentes en ce qui concerne cette question et je tiens à ma accepté de répondre. Cependant, j'ai maintenant une bien meilleure compréhension du concept et je suis en mesure de décider quand utiliser et quand ne pas.

Inconvénients:

  • Fil les questions de sécurité
  • Ne pas obéir à un "setter" demande lorsque la valeur transmise est null
  • Micro-optimisations
  • La gestion des exceptions devraient prendre place dans un constructeur
  • Besoin de vérifier la valeur null dans la catégorie " code

Pour:

  • Micro-optimisations
  • Les propriétés ne jamais retourner la valeur null
  • Retarder ou d'éviter le chargement des "gros" objets

La plupart des inconvénients ne sont pas applicables à ma bibliothèque actuelle, cependant j'aurais tester pour voir si le "micro-optimisations" sont en fait de l'optimisation de quoi que ce soit.

DERNIÈRE MISE À JOUR:

Ok, j'ai changé ma réponse. Ma question initiale était de savoir si ou pas c'est une bonne habitude. Et je suis maintenant convaincu qu'il ne l'est pas. Peut-être que je vais encore l'utiliser dans certaines parties de mon code actuel, mais pas de manière inconditionnelle et certainement pas tout le temps. Donc, je vais perdre mon habitude d'y réfléchir avant de l'utiliser. Merci à tous!

173voto

Daniel Hilgarth Points 90722

Ce que vous avez ici est un naïf - de la mise en œuvre de "initialisation".

Réponse courte:

À l'aide de l'initialisation tardive inconditionnellement n'est pas une bonne idée. Il a ses lieux, mais on doit prendre en considération les impacts de cette solution.

Contexte et d'explication:

La mise en œuvre concrète:
Nous allons d'abord regarder votre béton de l'échantillon et pourquoi je considère que sa mise en œuvre naïve:

  1. Elle viole le Principe de moindre Surprise (PPS). Lorsqu'une valeur est affectée à un bien immobilier, il est prévu que cette valeur est retournée. Dans votre mise en œuvre, ce n'est pas le cas pour l' null:

    foo.Bar = null;
    Assert.Null(foo.Bar); // This will fail
    
  2. Il présente quelques problèmes de threading: les Deux appelants d' foo.Bar sur différents threads peuvent potentiellement obtenir deux instances différentes de Bar , et l'un d'eux sera sans une connexion à l' Foo de l'instance. Toutes les modifications apportées à qui Bar de l'instance sont silencieusement perdu.
    C'est un autre cas de violation des POLS. Lorsque seule la valeur d'une propriété est accessible, il devrait être thread-safe. Alors que vous pourriez argumenter que la classe n'est tout simplement pas thread-safe, y compris la lecture de votre propriété, vous devez documenter correctement, que ce n'est pas le cas normal. En outre, l'introduction de cette question est inutile, comme nous le verrons bientôt.

En général:
Il est maintenant temps de regarder l'initialisation tardive en général:
L'initialisation différée est généralement utilisé pour retarder la construction des objets qui prennent beaucoup de temps à être construit ou qui prennent beaucoup de mémoire , une fois construit.
C'est une raison valable pour l'utilisation de l'initialisation tardive.

Toutefois, ces propriétés n'ont normalement pas de poseurs, qui se débarrasse de la première question a souligné ci-dessus.
En outre, un "thread-safe" la mise en œuvre serait utilisé comme Lazy<T> - pour éviter la deuxième question.

Même en tenant compte de ces deux points dans la mise en œuvre d'un paresseux de la propriété, les points suivants sont des problèmes généraux de ce modèle:

  1. La Construction de l'objet pourrait être infructueuse, résultant en une exception à partir d'une propriété de lecture. C'est encore une autre violation de POLS et devrait donc être évitée. Même la section sur propriétés dans les "lignes Directrices de Conception pour le Développement de Bibliothèques de classes" indique explicitement que les accesseurs de propriété ne devrait pas lancer des exceptions:

    Éviter de jeter des exceptions de la propriété des getters.

    Bien que les accesseurs devrait être de simples opérations sans conditions préalables. Si un getter peut lever une exception, envisager de modifier la propriété d'être une méthode.

  2. L'optimisation automatique par le compilateur sont blessés, à savoir l'in-lining et de la direction générale de la prévision. Veuillez voir le projet de Loi K de réponse pour une explication détaillée.

La conclusion de ces points est le suivant:
Pour chaque propriété qui est mise en œuvre paresseusement, vous devriez avoir examiné ces points.
Cela signifie, que c'est un cas par cas de décision et ne peut pas être considéré comme une meilleure pratique générale.

Ce modèle a sa place, mais il n'est pas un général de meilleures pratiques lors de la mise en œuvre de classes. Il ne doit pas être utilisé de manière inconditionnelle, à cause des raisons mentionnées ci-dessus.


Dans cette section, je voudrais discuter de certains points d'autres ont mis en avant comme arguments pour l'utilisation de l'initialisation tardive sans condition:

  1. La sérialisation:
    EricJ etats dans un commentaire:

    Un objet qui peut être sérialisé, elle ne sera pas du constructeur invoquée lorsqu'il est désérialisé (dépend du sérialiseur, mais beaucoup de ceux se comporter comme cela). Mettre le code d'initialisation dans le constructeur signifie que vous devez fournir un soutien supplémentaire pour la désérialisation. Ce modèle évite que le codage spécial.

    Il y a plusieurs problèmes avec cet argument:

    1. La plupart des objets ne sera jamais sérialisé. L'ajout d'une sorte de soutien pour lui quand il n'est pas nécessaire viole YAGNI.
    2. Quand une classe a besoin de l'appui de la sérialisation, il existe des moyens de l'activer sans une solution de contournement qui n'a rien à voir avec la sérialisation au premier coup d'œil.
  2. Micro-optimisation: Votre argument principal est que vous voulez construire les objets que lorsque quelqu'un accède à eux. Si vous êtes en train de parler de l'optimisation de l'utilisation de la mémoire.
    Je ne suis pas d'accord avec cet argument pour les raisons suivantes:

    1. Dans la plupart des cas, un peu plus d'objets dans la mémoire ont aucun impact sur quoi que ce soit. Les ordinateurs modernes ont assez de mémoire. Sans un cas de réels problèmes confirmé par un profiler, c'est la pré-maturité d'optimisation et il y a de bonnes raisons contre.
    2. Je reconnais le fait que, parfois, ce type d'optimisation est justifiée. Mais même dans ces cas, l'initialisation tardive ne semble pas être la bonne solution. Il y a deux raisons à parler contre elle:

      1. L'initialisation tardive potentiellement les performances de nuit. Peut-être que de façon marginale, mais que le projet de Loi, en réponse a montré, l'impact est plus grand qu'on pourrait le penser à première vue. Donc cette approche essentiellement des métiers de la performance par rapport à celle de la mémoire.
      2. Si vous avez une conception où il est commun que les cas d'utilisation pour utiliser uniquement les pièces de la classe, cette allusion à un problème avec la conception elle-même: La classe en question a probablement plus qu'une responsabilité. La solution serait de diviser la classe en plusieurs plus ciblée des classes.

50voto

AMissico Points 12837

C'est un bon choix de conception. Fortement recommandé pour le code de la bibliothèque ou des classes de base.

Il est appelé par certains "initialisation" ou "en retard d'initialisation" et il est généralement considéré par tous comme un bon choix de conception.

Tout d'abord, si vous initialisez dans la déclaration de niveau de la classe de variables ou d'un constructeur, puis lorsque votre objet est construit, vous avez la surcharge de la création d'une ressource qui ne peut jamais être utilisé.

Deuxièmement, la seule ressource est créée si nécessaire.

Troisième, vous évitez le nettoyage à un objet qui n'a pas été utilisé.

Enfin, il est plus facile de gérer l'initialisation des exceptions qui peuvent se produire dans la propriété, alors les exceptions qui se produisent lors de l'initialisation de la classe les variables de niveau ou le constructeur.

Il y a des exceptions à cette règle.

Concernant les performances de l'argument de la vérification supplémentaire pour l'initialisation dans le "get" la propriété, c'est insignifiant. L'initialisation et l'élimination d'un objet est un plus significatif de performances qu'un simple pointeur null vérifier avec un saut.

Les lignes Directrices de conception pour le Développement de Bibliothèques de classes à http://msdn.microsoft.com/en-US/library/vstudio/ms229042.aspx

Concernant l' Lazy<T>

Le générique de l' Lazy<T> de la classe a été créée pour que l'affiche veut, voir l'Initialisation tardive à http://msdn.microsoft.com/en-us/library/dd997286(v=vs. 100).aspx. Si vous avez d'anciennes versions de .NET, vous devez utiliser le code modèle illustré dans la question. Ce modèle de code est devenu tellement commun que Microsoft a jugé bon d'inclure une classe dans le dernier .Bibliothèques NET pour le rendre plus facile à mettre en œuvre le modèle. En outre, si vos besoins de mise en œuvre la sécurité des threads, vous devez l'ajouter.

Les Types de Données primitifs et Simple des Classes

Obvioulsy, vous n'allez pas utiliser paresseux-initialisation pour le type de données primitif ou simple de la classe à utiliser comme List<string>.

Avant de Commenter au sujet de Paresseux

Lazy<T> a été introduit en .NET 4.0, donc merci de ne pas ajouter encore un autre commentaire au sujet de cette classe.

Avant de Commenter sur la Micro-Optimisations

Lorsque vous créez des bibliothèques, vous devez tenir compte de toutes les optimisations. Par exemple, dans le .NET de classes, vous verrez peu de tableaux utilisés pour la classe Boolean variables dans le code afin de réduire la consommation de mémoire et de fragmentation de la mémoire, pour ne citer que deux "micro-optimisations".

Concernant Les Interfaces Utilisateur

Vous n'allez pas utiliser l'initialisation tardive pour les classes qui sont utilisés directement par l'interface utilisateur. La semaine dernière j'ai passé la meilleure partie de la journée de retrait de chargement paresseux de huit collections utilisé dans une vue-modèle pour les listes déroulantes. J'ai un LookupManager qui gère le chargement paresseux et la mise en cache des collections nécessaire par n'importe quel utilisateur de l'interface de l'élément.

"Poseurs"

Je n'ai jamais utilisé un ensemble de la propriété (des"poseurs") pour tous les paresseux chargé de la propriété. Par conséquent, vous ne permettrait jamais foo.Bar = null;. Si vous avez besoin d' Bar alors je voudrais créer une méthode appelée SetBar(Bar value) et ne pas utiliser paresseux-initialisation

Collections

Classe collection de propriétés sont toujours initialisée lors de sa déclaration parce qu'ils ne doivent jamais être null.

Des Classes Complexes

Permettez-moi de répéter les choses différemment, vous utilisez paresseux-initialisation pour des classes complexes. Qui sont généralement, mal conçu classes.

Enfin

Je n'ai jamais dit de le faire pour toutes les classes et dans tous les cas. C'est une mauvaise habitude.

17voto

Matías Fidemraizer Points 16842

Considérez vous que la mise en œuvre de ce modèle en utilisant `` ?

En plus de créer facilement des objets chargés en paresseux, vous obtenez la sécurité des threads pendant que l’objet est initialisé :

Comme d’autres l’ont dit, vous paresseusement-charge les objets si ils sont vraiment lourds-ressource ou il faut un certain temps à charger lors de la construction objet.

9voto

Colin Mackay Points 9303

Je pense que cela dépend de ce que vous êtes initialisation. J’ai probablement ne serait pas le faire pour obtenir une liste car le coût de construction est assez petit, donc il peut aller dans le constructeur. Mais s’il s’agissait d’une liste préremplie puis j’ai probablement ne serait pas jusqu'à ce qu’elle était nécessaire pour la première fois.

Fondamentalement, si le coût de la construction l’emporte sur le coût de faire un contrôle conditionnel sur chaque accès puis paresseux créez-la. Si ce n’est pas le cas, faites-le dans le constructeur.

8voto

Luis Tellez Points 1770

L’inconvénient que je vois c’est que si vous voulez demander que si Bars est null, il ne serait jamais et vous créerait la liste là.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X