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

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

        public object Prop2 { get; set; }

        public object Prop3 { get; set; }
    }

Supposons que j'ai un objet myObjectde MyClasset 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;
Cloches
la source
14
On ne peut pas vraiment répondre sans contexte.
CodesInChaos
2
@CodesInChaos, en effet. Exemple spécifique dans lequel la création de nouveaux objets peut ne pas être préférable: regroupement d'objets et tentative de minimiser les allocations pour traiter avec le GC.
XNargaHuntress
@ XGundam05 Mais même dans ce cas, il est très peu probable que cette technique suggérée dans le PO soit la manière appropriée de la gérer
Ben Aaronson
2
Suppose I have an object myObject of MyClass and I need to reset its properties- Si la réinitialisation des propriétés de votre objet est nécessaire ou s'il est utile de modéliser le domaine problématique, pourquoi ne pas créer une reset()méthode pour MyClass. Voir la réponse de HarrisonPaine ci
Brandin

Réponses:

49

Instancier un nouvel objet est toujours mieux, alors vous avez 1 place 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.

gbjbaanb
la source
6
Troisième option possible: myObject = new MyClass(oldObject);. Bien que cela impliquerait une copie exacte de l'objet d'origine dans une nouvelle instance.
AmazingDreams
Je pense que new MyClass(oldObject)c'est la meilleure solution pour les cas où l'initialisation coûte cher.
rickcnagy
8
Les constructeurs de copie sont plus de style C ++. .NET semble favoriser une méthode Clone () qui renvoie une nouvelle instance qui est une copie exacte.
Eric
2
Le constructeur ne pouvait facilement rien faire d'autre que d'appeler une méthode publique Initialize () afin que vous ayez toujours ce point d'initialisation unique qui pourrait être invoqué n'importe quel point pour créer efficacement un "nouvel" nouvel objet. J'ai utilisé des bibliothèques qui ont quelque chose comme une interface IInitialize pour des objets qui peuvent être utilisés dans des pools d'objets et réutilisés pour des performances.
Chad Schouggins du
16

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 classe Aet classe Bsont toutes deux des instances de classe passées, Celles doivent savoir si elles passeront jamais la même instance, et si oui, si l'autre l'utilise toujours. 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 séparée 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 Fractionclasse 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, afin que vous puissiez utiliser le modèle de poids de mouche ou similaire à la place.


En guise de 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 une public Resetmé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.

Ben Aaronson
la source
Je pense qu'un cas d'utilisation beaucoup plus important pour réinitialiser plutôt que de créer un nouvel objet est si vous réinitialisez réellement l'objet. C'EST À DIRE. si vous voulez que d'autres classes qui pointent vers l'objet voient un nouvel objet après l'avoir réinitialisé.
Taemyr
@Taemyr Peut-être, mais le PO exclut explicitement cela dans la question
Ben Aaronson
9

É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 Reset()méthode, plutôt que de s'appuyer sur l'appel du constructeur ou de définir manuellement ses propriétés.

Harrison Paine
la source
5

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; }
}
Antoine Trouve
la source
Exactement ce à quoi je voulais répondre. C'est le meilleur des deux mondes - Et selon le cas d'utilisation, le seul choix viable (regroupement d'instances)
Falco
2

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, souvent , " 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 il peut être utile du point de vue des performances pour la classe d'inclure une méthode pour réinitialiser une instance à aimer- nouvelle condition. 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 retourner 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 refparamètre 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). Bien que l'utilisation List<T>puisse être plus agréable que l'utilisation d'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.

supercat
la source
1

Je pense que la plupart des gens qui répondent à l'idée de 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 (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.

Jonathan Lavering
la source