Envoi de référence d’object avant sa construction

J’ai vu le code suivant dans l’une de nos applications:

public class First() { private Second _second; public First() { _second = new Second(this); // Doing some other initialization stuff, } } public class Second { public Second(First f) { } } 

Dans le constructeur First() , n’est-ce pas mauvais que nous envoyions une référence de classe First() avant qu’elle soit entièrement construite? Je pense que l’object n’est construit que lorsque la logique de contrôle quitte le constructeur.

Ou est-ce que ça va?

Ma question est la suivante: dans le constructeur First (), n’est-ce pas grave que nous envoyions une référence de classe First () AVANT qu’elle soit entièrement construite?

Quelque peu. Cela peut certainement poser problème.

Si le constructeur Second ne contient qu’une référence pour une utilisation ultérieure, ce n’est pas trop grave. Si, par contre, le constructeur Second renvoie dans First :

 public Second(First f) { f.DoSomethingUsingState(); } 

… et l’état n’a pas encore été mis en place, alors ce serait bien sûr une très mauvaise chose. Si vous appelez une méthode virtuelle sur First cela pourrait être encore pire – vous pourriez finir par appeler un code qui n’a même pas encore eu la chance d’exécuter un de ses corps de constructeurs (bien que ses initialiseurs de variable aient été exécutés).

En particulier, les champs en readonly peuvent être vus en premier avec une valeur puis plus tard avec une autre …

J’ai blogué à ce sujet il y a quelque temps, ce qui pourrait fournir plus d’informations.

Bien sûr, sans faire ce genre de choses, il est assez difficile de créer deux objects immuables qui se réfèrent mutuellement …

Si vous rencontrez ce modèle, vous pouvez vérifier si cela peut être modifié à la place:

 public class First() { private Second _second; public First() { _second = new Second(this); // Doing some other initialization stuff, } private class Second { public Second(First f) { } } } 

Passer la dépendance dans le constructeur implique une sorte de couplage étroit entre les deux classes, car First doit faire confiance à Second qui sait ce qu’il fait et ne pas essayer de compter sur l’état non initialisé de First. Ce type de couplage fort est plus approprié lorsque Second est une sous-classe privée nestede (et donc un détail d’implémentation clair) ou éventuellement lorsqu’il s’agit d’une classe interne.

La réponse est que ça dépend. Généralement, cela serait considéré comme une mauvaise idée en raison des conséquences potentielles.

Plus précisément, cependant, tant que Second n’utilise rien de First avant qu’il soit construit, vous devriez vous en sortir. Si vous ne pouvez pas le garantir d’une manière ou d’une autre, vous pourriez certainement rencontrer des problèmes.

Oui c’est un peu mauvais. Il est possible de faire des choses dans les champs de First avant qu’il ne soit complètement initialisé, ce qui provoquerait un comportement indésirable ou indéfini.

La même chose se produit lorsque vous appelez une méthode virtuelle à partir de votre constructeur.

Contrairement à, par exemple, C ++, CLR n’a aucune notion d’objects entièrement construits ou incomplets. Dès que l’allocateur de mémoire retourne un object mis à zéro et avant l’exécution du constructeur, il est prêt à être utilisé (du sharepoint vue du CLR). Il a son type final, les appels aux méthodes virtuelles invoquent le remplacement le plus dérivé, etc. Vous pouvez utiliser this dans le corps du constructeur, appeler les méthodes virtuelles, etc. Cela peut en effet causer des problèmes avec l’ordre d’initialisation.

Il est vrai que cela pourrait entraîner des problèmes, comme vous l’avez décrit. Par conséquent, il est généralement conseillé d’exécuter des commandes telles que _second = new Second(this); seulement après les autres éléments d’initialisation impliqués par votre commentaire.

Parfois, ce modèle est la seule solution pour stocker des références réciproques entre deux objects. Dans de nombreux cas, cependant, cela se produit de manière à ce que la classe recevant l’instance éventuellement non complètement initialisée soit étroitement couplée à la classe référencée (par exemple, écrite par le même auteur, partie de la même application ou classe nestede). éventuellement privé). Dans de tels cas, les effets négatifs peuvent être évités car l’auteur de Second connaît (ou a peut-être même écrit) les éléments internes de First .

Cela dépend du scénario, mais cela peut conduire à un comportement difficile à prévoir. Si Second fait quelque chose avec First dans le constructeur, ce comportement peut devenir mal défini une fois que vous modifiez le constructeur de First . Le guidage de constructeur supplémentaire suggère également que vous ne devriez pas appeler des méthodes virtuelles ou abstraites (sur la classe construite) dans un constructeur, car cela peut entraîner des conséquences similaires lorsque le comportement peut être difficile à raisonner.

La réponse à cette question dépend de la nature de la relation entre le First et le Second .

Pensez à quelle sorte d’object pourrait être composé d’ un autre object, qui est lui-même composé de (ou nécessite pour son initialisation) l’object de type First . Dans de telles situations, vous devriez vous méfier de la création de graphiques d’objects avec des cycles.

Néanmoins, il existe de nombreuses situations légitimes dans lesquelles un cycle doit se produire dans un graphe d’objects. Si First se base sur l’état de Second pour effectuer son initialisation, vous devez conserver la méthode telle quelle et c’est généralement bien. Si Second repose sur l’état de First pour effectuer sa propre initialisation, vous devez probablement réorganiser le constructeur en tant que tel:

  public First() { // Doing some other initialization stuff, _second = new Second(this); } 

Si les deux affirmations précédentes sont vraies (la Second dépend de l’état de First et la First dépend de l’état de Second ), vous devriez certainement revoir votre conception et déterminer plus précisément la nature de la relation entre First et Second . (Peut-être devrait-il y avoir un object Third contenant une référence à la fois au First et au Second , et la relation entre ces deux derniers devrait être arbitrée par un Third .)