web-dev-qa-db-fra.com

Créer un nouvel objet ou réinitialiser chaque propriété?

 public class MyClass
    {
        public object Prop1 { get; set; }

        public object Prop2 { get; set; }

        public object Prop3 { get; set; }
    }

Supposons que j'ai un objet myObject de MyClass et que je doive réinitialiser ses propriétés, est-il préférable de créer un nouvel objet ou de réaffecter chaque propriété? Supposons que je n'ai aucune utilisation supplémentaire avec l'ancienne instance.

myObject = new MyClass();

ou

myObject.Prop1 = null;
myObject.Prop2 = null;
myObject.Prop3 = null;
29
Bells

Instancier un nouvel objet est toujours mieux, alors vous avez 1 endroit pour initialiser les propriétés (le constructeur) et pouvez facilement le mettre à jour.

Imaginez que vous ajoutez une nouvelle propriété à la classe, vous préférez mettre à jour le constructeur plutôt que d'ajouter une nouvelle méthode qui réinitialise également toutes les propriétés.

Maintenant, il y a des cas où vous voudrez peut-être réutiliser un objet, un cas où une propriété est très coûteuse à réinitialiser et que vous voudriez le conserver. Ce serait cependant plus spécialisé et vous auriez des méthodes spéciales pour réinitialiser toutes les autres propriétés. Vous voudriez toujours créer un nouvel objet parfois même pour cette situation.

49
gbjbaanb

Vous devriez certainement préférer créer un nouvel objet dans la grande majorité des cas. Problèmes de réaffectation de toutes les propriétés:

  • Nécessite des setters publics sur toutes les propriétés, ce qui limite considérablement le niveau d'encapsulation que vous pouvez fournir
  • Savoir si vous avez une utilisation supplémentaire pour l'ancienne instance signifie que vous devez savoir partout où l'ancienne instance est utilisée. Donc, si la classe A et la classe B sont toutes deux des instances passées de la classe C, alors elles doivent savoir si elles passeront jamais la même instance, et si oui, si l'autre l'utilise encore. Cela couple étroitement les classes qui, autrement, n'ont aucune raison d'être.
  • Conduit à un code répété - comme l'a indiqué gbjbaanb, si vous ajoutez un paramètre au constructeur, partout où l'appel du constructeur échouera à la compilation, il n'y a aucun danger de manquer un point. Si vous venez d'ajouter une propriété publique, vous devrez vous assurer de trouver et de mettre à jour manuellement chaque endroit où les objets sont "réinitialisés".
  • Augmente la complexité. Imaginez que vous créez et utilisez une instance de la classe dans une boucle. Si vous utilisez votre méthode, vous devez maintenant effectuer une initialisation distincte la première fois dans la boucle ou avant le début de la boucle. Dans les deux cas, vous devez écrire du code supplémentaire pour prendre en charge ces deux méthodes d'initialisation.
  • Signifie que votre classe ne peut pas se protéger d'être dans un état invalide. Imaginez que vous avez écrit une classe Fraction avec un numérateur et un dénominateur et que vous vouliez faire en sorte qu'elle soit toujours réduite (c'est-à-dire que le pgcd du numérateur et du dénominateur était 1). C'est impossible à faire si vous voulez permettre aux gens de définir le numérateur et le dénominateur publiquement, car ils peuvent passer par un état invalide pour passer d'un état valide à un autre. Par exemple. 1/2 (valide) -> 2/2 (invalide) -> 2/3 (valide).
  • N'est pas du tout idiomatique pour la langue dans laquelle vous travaillez, augmentant le frottement cognitif pour quiconque maintient le code.

Ce sont tous des problèmes assez importants. Et ce que vous obtenez en échange du travail supplémentaire que vous créez n'est ... rien. La création d'instances d'objets est, en général, incroyablement bon marché, de sorte que les performances seront presque toujours totalement négligeables.

Comme l'autre réponse l'a mentionné, le seul moment où les performances peuvent être une préoccupation pertinente est si votre classe effectue des travaux de construction considérablement coûteux. Mais même dans ce cas, pour que cette technique fonctionne, vous devez être en mesure de séparer la partie coûteuse des propriétés que vous réinitialisez, de sorte que vous pourrez utiliser le modèle de poids de mouche ou similaire à la place.


En remarque, certains des problèmes ci-dessus pourraient être quelque peu atténués en n'utilisant pas de setters et en ayant à la place un public Reset méthode sur votre classe qui prend les mêmes paramètres que le constructeur. Si, pour une raison quelconque, vous vouliez emprunter cette voie de réinitialisation, ce serait probablement une bien meilleure façon de le faire.

Pourtant, la complexité et la répétition supplémentaires qui s'ajoutent, ainsi que les points ci-dessus qu'il ne traite pas, sont toujours un argument très convaincant contre le faire, surtout lorsqu'il est comparé aux avantages inexistants.

16
Ben Aaronson

Étant donné l'exemple très générique, c'est difficile à dire. Si "réinitialiser les propriétés" a un sens sémantique dans le cas du domaine, il sera plus logique pour le consommateur de votre classe d'appeler

MyObject.Reset(); // Sets all necessary properties to null

Que

MyObject = new MyClass();

Je n'exigerais JAMAIS de faire appel au consommateur de votre classe

MyObject.Prop1 = null;
MyObject.Prop2 = null; // and so on

Si la classe représente quelque chose qui peut être réinitialisé, elle doit exposer cette fonctionnalité via une méthode Reset(), plutôt que de s'appuyer sur l'appel du constructeur ou de définir manuellement ses propriétés.

9
Harrison Paine

Comme le suggèrent Harrison Paine et Brandin, je réutiliserais le même objet et factoriserais l'initialisation des propriétés dans une méthode Reset:

public class MyClass
{
    public MyClass() { this.Reset() }

    public void Reset() {
        this.Prop1 = whatever
        this.Prop2 = you name it
        this.Prop3 = oh yeah
    }

    public object Prop1 { get; set; }

    public object Prop2 { get; set; }

    public object Prop3 { get; set; }
}
5
Antoine Trouve

Si le modèle d'utilisation prévu pour une classe est qu'un seul propriétaire conservera une référence à chaque instance, aucun autre code ne conservera de copie des références, et il sera très courant pour les propriétaires d'avoir des boucles qui doivent, plusieurs fois, "remplissez" une instance vide, utilisez-la temporairement et n'en aurez plus jamais besoin (une classe commune répondant à un tel critère serait StringBuilder), alors cela peut être utile du point de vue des performances pour la classe pour inclure une méthode pour réinitialiser une instance à une condition comme neuve. Une telle optimisation ne vaut probablement pas grand-chose si l'alternative ne nécessite que la création de quelques centaines d'instances, mais le coût de création de millions ou de milliards d'instances d'objets peut s'additionner.

Sur une note connexe, il existe quelques modèles qui peuvent être utilisés pour les méthodes qui doivent renvoyer des données dans un objet:

  1. La méthode crée un nouvel objet; renvoie la référence.

  2. La méthode accepte une référence à un objet mutable et la remplit.

  3. La méthode accepte une variable de type référence en tant que paramètre ref et utilise l'objet existant si approprié, ou modifie la variable pour identifier un nouvel objet.

La première approche est souvent la plus simple sémantiquement. Le second est un peu gênant du côté appelant, mais peut offrir de meilleures performances si un appelant est souvent capable de créer un objet et de l'utiliser des milliers de fois. La troisième approche est un peu maladroite sur le plan sémantique, mais peut être utile si une méthode doit renvoyer des données dans un tableau et que l'appelant ne connaîtra pas la taille de tableau requise. Si le code appelant contient la seule référence au tableau, réécrire cette référence avec une référence à un tableau plus grand équivaudra sémantiquement à simplement agrandir le tableau (ce qui est le comportement souhaité sémantiquement). En utilisant List<T> peut être plus agréable que d'utiliser un tableau redimensionné manuellement dans de nombreux cas, les tableaux de structures offrent une meilleure sémantique et de meilleures performances que les listes de structures.

2
supercat

Je pense que la plupart des gens qui souhaitent favoriser la création d'un nouvel objet n'ont pas de scénario critique: la collecte des ordures (GC). GC peut avoir un réel impact sur les performances dans les applications qui créent beaucoup d'objets (jeux de réflexion ou applications scientifiques).

Disons que j'ai un arbre d'expression qui représente une expression mathématique, où les nœuds internes sont des nœuds de fonction (ADD, SUB, MUL, DIV) et les nœuds de feuille sont des nœuds terminaux (X, e, PI). Si ma classe de nœuds a une méthode Evaluate (), elle fera probablement appel de manière récursive aux enfants et collectera des données via un nœud de données. À tout le moins, tous les nœuds terminaux devront créer un objet Data, puis ceux-ci pourront être réutilisés en remontant dans l'arbre jusqu'à ce que la valeur finale soit évaluée.

Supposons maintenant que j'ai des milliers de ces arbres et que je les ai évalués en boucle. Tous ces objets de données vont déclencher un GC et provoquer un impact sur les performances, un gros problème (jusqu'à 40% de perte d'utilisation du processeur pour certaines exécutions dans mon application - j'ai exécuté un profileur pour obtenir les données).

Une solution possible? Réutilisez ces objets de données et appelez simplement certains .Reset () sur eux après avoir fini de les utiliser. Les nœuds terminaux n'appelleront plus 'new Data ()', ils appelleront une méthode Factory qui gère le cycle de vie de l'objet.

Je le sais parce que j'ai une application qui a rencontré ce problème et qui l'a résolu.

1
Jonathan Lavering