Pourquoi une boucle while (true) dans un constructeur est-elle réellement mauvaise?

Bien que la question soit générale, ma portée est plutôt C#, étant donné que des langages tels que C ++ ont une sémantique différente en ce qui concerne l'exécution du constructeur, la gestion de la mémoire, le comportement indéfini, etc.

Quelqu'un m'a posé une question intéressante à laquelle il n'a pas été facile de répondre.

Pourquoi (ou est-ce du tout?) Considéré comme une mauvaise conception permettant à un constructeur de classe de commencer une boucle sans fin (c'est-à-dire une boucle de jeu)?

Il y a quelques concepts qui sont cassés par ceci:

  • comme le principe du moindre étonnement, l'utilisateur ne s'attend pas à ce que le constructeur se comporte comme ceci.
  • Les tests unitaires sont plus difficiles car vous ne pouvez pas créer cette classe ni l'injecter car elle ne quitte jamais la boucle.
  • La fin de la boucle (fin du jeu) est alors conceptuellement le moment où le constructeur se termine, ce qui est également étrange.
  • Techniquement, une telle classe n'a pas de membre public, à l'exception du constructeur, ce qui la rend plus difficile à comprendre (en particulier pour les langues où aucune implémentation n'est disponible)

Et puis il y a des problèmes techniques:

  • Le constructeur ne finit jamais, alors que se passe-t-il avec GC ici? Cet objet est-il déjà dans la génération 0?
  • Dériver d'une telle classe est impossible ou du moins très compliqué car le constructeur de base ne renvoie jamais

Y a-t-il quelque chose de plus manifestement mauvais ou sournois avec une telle approche?

47
ajouté édité
Vues: 3
C'est moche, mais au moins pour les classes scellées, il n'y a pas de gros problème.
ajouté l'auteur user8669, source
Votre question indique déjà 6 raisons pour ne pas l’utiliser, et j’aimerais en voir une seule en sa faveur. Vous pouvez également demander pourquoi c'est une mauvaise idée de mettre une boucle while (true) dans un configurateur de propriétés: new Game (). RunNow = true ?
ajouté l'auteur Tavo, source
Pourquoi cherchez-vous quelque chose de plus manifestement mauvais ou sournois? Les points que vous avez énumérés ne sont-ils pas déjà assez mauvais et sournois? Quelle est votre norme de comparaison ici?
ajouté l'auteur Kevin Krumwiede, source
Pourquoi pas de membres du public? Il pourrait y avoir des discussions.
ajouté l'auteur rsn8887, source
Le but des constructeurs est de créer des choses. Lorsque j'écris Jeu g = nouveau jeu (); , je m'attends à ce que g contienne un nouvel objet Jeu .
ajouté l'auteur immibis, source
@JonAnderson while (true) est bien utilisé dans les microcontrôleurs intégrés, quand il est autre chose qu'une puissance réduite. Pour le code "normal", je suis d'accord.
ajouté l'auteur pipe, source
@JonAnderson Il y a break; pour cela. Je ne dis pas que vous devriez mettre une boucle while (true) dans votre constructeur, mais dire qu'il n'y a pas de moyen clair et net de l'arrêter est faux.
ajouté l'auteur Uwe Plonus, source
Est-ce réellement mauvais? Cela dépend du langage, de son compilateur (ou interprète) et des éventuels effets imprévus ou comportements non définis. Cela pourrait effectivement être bien. (En réalité, il se peut que ce ne soit pas le cas.) Le seul problème ici est que vous enfreignez les schémas et conventions OO établis, ce qui se transforme toujours en un débat religieux. Alors ... tu peux faire ça? Peut être. Devrais-tu? Seulement si cela ne vous dérange pas d'être crucifié par la communauté.
ajouté l'auteur Sean Mackesey, source
Un argument positif peut être (si le constructeur ne divulgue aucune référence à this ): aucun autre thread ne pouvant obtenir une référence à l'objet (après tout, il est toujours en construction), vous ne le faites pas. pas besoin de synchronisation sur les variables membres (qui pourraient autrement devenir coûteuses)
ajouté l'auteur Junji Shimagaki, source
Pire encore, imaginez si un futur responsable crée une classe dérivée qui remplace une méthode virtuelle qui dépend maintenant des membres de la classe dérivée et qui est appelée par le constructeur de base. (Je n'ai pas vérifié cela, je m'attendrais à ce que l'application se bloque ou autorise un comportement indéfini dans un environnement géré)
ajouté l'auteur Junji Shimagaki, source
Pourquoi est-il bon? Si vous déplacez simplement votre boucle principale vers une méthode (refactoring très simple), l'utilisateur peut écrire un code non surprenant comme ceci: var g = new Game {...}; g.MainLoop ();
ajouté l'auteur Brandin, source
Analogie extrême: pourquoi nous disons que Hitler a eu tort? À l'exception de la discrimination raciale, du début de la Seconde Guerre mondiale, du meurtre de millions de personnes, de la violence [etc. etc. pour plus de 50 autres raisons], il n'a rien fait de mal. Bien sûr, si vous supprimez une liste arbitraire de raisons pour lesquelles quelque chose ne va pas, vous pouvez aussi bien conclure que cette chose est bonne, pour littéralement n'importe quoi.
ajouté l'auteur Tobias Sette, source
Une généralisation de ce serait une boucle infinie dans toute méthode avec une valeur de retour non vide. La présence d'une valeur de retour (ou la possibilité apparente de renvoyer une instance) est trompeuse si elle ne renvoie jamais une valeur.
ajouté l'auteur Manisha Shekhawat, source
Pourquoi utilisez-vous un constructeur plutôt qu'une simple fonction?
ajouté l'auteur Bergi, source
J'ai l'impression que toutes les réponses se résument à "Je ne peux pas imaginer un cas d'utilisation, alors ne l'utilisez pas". Avez-vous réellement un cas d'utilisation qui vous invite à faire quelque chose comme ceci dans un constructeur plutôt que dans une méthode ou une fonction?
ajouté l'auteur woldo321, source
Je me demande si cette question n’a pas été fermée pour la bêtise.
ajouté l'auteur t3chb0t, source
Je voudrais aller un peu plus loin et dire que while (true) est mauvais, peu importe où il est utilisé. Cela implique qu'il n'y a pas de moyen clair et propre pour arrêter la boucle. Dans votre exemple, la boucle ne devrait-elle pas s'arrêter à la sortie du jeu ou perdre le focus?
ajouté l'auteur J_H, source
En ce qui concerne le ramasse-miettes (votre problème technique), il n’y aurait rien de spécial à ce sujet. L'instance existe avant que le code constructeur ne soit réellement exécuté. Dans ce cas, l'instance est toujours accessible et ne peut donc pas être réclamée par GC. Si GC commence, cette instance survivra et, à chaque occurrence de GC, l'objet sera promu à la génération suivante, conformément à la règle habituelle. Que la GC ait lieu ou non dépend de si (suffisamment) de nouveaux objets sont créés ou non.
ajouté l'auteur Amida, source
@KevinKrumwiede J'étais trop longtemps dans le comportement indéfini C ++ et m'attendais à quelque chose de bizarre ici, ce que je ne comprenais pas encore.
ajouté l'auteur swe, source
@Brandin J'en suis conscient, la refactorisation en méthode publique est facile et résout le problème, mais cela n'explique aucunement pourquoi cela pourrait être une mauvaise idée.
ajouté l'auteur swe, source

9 Réponses

Quel est le but d'un constructeur? Il retourne un objet nouvellement construit. Que fait une boucle infinie? Ça ne revient jamais. Comment le constructeur peut-il retourner un objet nouvellement construit s'il ne retourne pas du tout? Ça ne peut pas.

Ergo, une boucle infinie rompt le contrat fondamental du constructeur: construire quelque chose.

249
ajouté
Peut-être qu'ils voulaient mettre tout ce qui est (vrai) avec une pause au centre? Risqué, mais légitime.
ajouté l'auteur jsims281, source
@ JörgWMittag bien, alors, oui, ne mettez pas cela dans un constructeur.
ajouté l'auteur jsims281, source
@ DocBrown: Je pense que l'important est le contrat implicite d'un constructeur, ou de le regarder de l'autre côté, les attentes du programmeur. Sauf si vous êtes un programmeur Haskell, vous vous attendez à ce qu'une "fonction" (au sens C du mot) "fasse des choses" et échoue ou finisse à l'occasion. C'est juste quelque chose que nous acceptons. (Et en fait, même Haskell a une récursion infinie, des exceptions et unsafePerformIO .) Mais nous ne nous attendons pas à ce qu'un constructeur "fasse des choses". Nous nous attendons à ce qu’il construise une valeur, qui est (conceptuellement) une chose très simple.
ajouté l'auteur Lawrence B. Crowell, source
@ JanDvorak: Eh bien, c'est une question différente. L'OP spécifiait explicitement "jamais terminé" et mentionnait une boucle d'événement.
ajouté l'auteur Lawrence B. Crowell, source
Je suis d'accord, c'est correct - au moins. d'un point de vue académique. Même chose pour chaque fonction qui retourne quelque chose.
ajouté l'auteur Peter LeFanu Lumsdaine, source
Faire les choses "à côté du livre" présente de nombreux avantages ... Appelez-moi paresseux, mais mon préféré est de ne pas avoir à écrire un tout nouveau livre pour apprendre aux autres comment utiliser ma création.
ajouté l'auteur Newtopian, source
@ Joshua: c'est vrai, on pourrait le faire de cette façon, mais n'importe quel code du constructeur de la sous-classe ne serait jamais appelé, ce qui signifie essentiellement que la sous-classe n'est jamais construite ... Je me demande ce qui arriverait si sous-classe, on devait utiliser de nouveaux champs non présents dans la classe de base? ... Encore une fois, on pourrait créer une fonction init pouvant être surchargée pour initialiser en sachant que le constructeur ne serait pas réellement appelé ... Cela ressemble à un énormément de difficulté à se déplacer sans avoir à appeler start() sur l'instance renvoyée par un constructeur plus simple.
ajouté l'auteur Newtopian, source
Imaginez le pauvre gazon qui crée une sous-classe de ladite classe ...
ajouté l'auteur Newtopian, source
Pour être techniquement correct: un constructeur ne renvoie pas l'objet correspondant. Le constructeur est une fonction comme une autre. La différence est qu’il est exécuté automatiquement et immédiatement après l’instanciation d’un objet.
ajouté l'auteur Radu Murzea, source
@Radu Murzea: Lorsque j'ai tenté d'implémenter un langage orienté objet géré trouvé au niveau de l'assemblage, il est logique de demander au constructeur de renvoyer l'objet. La disposition empêchait le code extérieur de pouvoir jamais contenir une référence à un objet non construit. La façon dont cela fonctionnait était nouveau, un pointeur sur le constructeur était passé comme s'il s'agissait d'une fonction ordinaire.
ajouté l'auteur Joshua, source
@Newtopian: l'héritage d'un tel objet peut être utile en C# en raison de la manière dont la table virtuelle est remise. Le dépassement des méthodes appelées par le constructeur est raisonnable.
ajouté l'auteur Joshua, source
J'ai déjà vu des choses bidouiller dans mon temps mais cela aurait besoin d'une bonne raison de justifier. Principe du moindre étonnement en effet.
ajouté l'auteur candied_orange, source
@ Jörg W Mittag: Et si la valeur à construire est le meilleur score du jeu? ^^ (juste un peu de matière à réflexion)
ajouté l'auteur Junji Shimagaki, source
@hoffmale c'est un point de vue très ésotérique, mais je le considérerais certainement comme une matière à réflexion.
ajouté l'auteur The Great Duck, source

Y a-t-il quelque chose de plus évident ou de plus sournois avec une telle approche?

Oui bien sûr. C'est inutile, inattendu, inutile, sans élégance. Il viole les concepts modernes de conception de classe (cohésion, couplage). Il rompt le contrat de méthode (un constructeur a un travail défini et n'est pas simplement une méthode aléatoire). Ce n'est certainement pas très facile à maintenir, les futurs programmeurs passeront beaucoup de temps à comprendre ce qui se passe et à deviner les raisons pour lesquelles cela a été fait.

Rien de tout cela n'est un "bug" dans le sens où votre code ne fonctionne pas. Mais il y aura probablement des coûts secondaires énormes (par rapport au coût de l'écriture initiale du code) à long terme en rendant le code plus difficile à maintenir (c.-à-d. Difficile d'ajouter des tests, difficile à réutiliser, difficile à déboguer, difficile à étendre, etc. .)

La plupart des améliorations, parmi les plus modernes, des méthodes de développement logiciel sont apportées spécifiquement pour faciliter le processus d’écriture/test/débogage/maintenance du logiciel. Tout cela est contourné par des choses comme celle-ci, où le code est placé de manière aléatoire parce qu'il "fonctionne".

Malheureusement, vous rencontrerez régulièrement des programmeurs complètement ignorants de tout cela. Ça marche, c'est ça.

Pour terminer avec une analogie (un autre langage de programmation, le problème est de calculer 2 + 2 ):

$sum_a = `bash -c "echo $((2+2)) 2>/dev/null"`;   # calculate
chomp $sum_a;                         # remove trailing \n
$sum_a = $sum_a + 0;                  # force it to be a number in case some non-digit characters managed to sneak in

$sum_b = 2+2;

Quel est le problème avec la première approche? Il renvoie 4 après un laps de temps raisonnable; c'est correct. Les objections (avec toutes les raisons habituelles qu'un développeur peut donner pour les réfuter) sont les suivantes:

  • Plus difficile à lire (mais bon, un bon programmeur peut le lire aussi facilement, et nous l’avons toujours fait comme ça; si vous voulez, je peux le reformater dans une méthode!)
  • Plus lent (mais ce n'est pas dans un endroit crucial pour le timing, nous pouvons donc l'ignorer, et en outre, il vaut mieux optimiser uniquement lorsque cela est nécessaire!)
  • utilise beaucoup plus de ressources (un nouveau processus, plus de RAM, etc. - mais, notre serveur est plus que rapide)
  • Introduit des dépendances sur bash (mais il ne fonctionnera jamais sous Windows, Mac ou Android)
  • Et toutes les autres raisons mentionnées ci-dessus.
44
ajouté
"GC pourrait bien être dans un état de verrouillage exceptionnel lors de l'exécution d'un constructeur" - pourquoi? L'allocateur alloue d'abord, puis lance le constructeur en tant que méthode ordinaire. Il n'y a rien de vraiment spécial à ce sujet, n'est-ce pas? De plus, l’allocateur doit autoriser de nouvelles attributions (et celles-ci pourraient déclencher des situations de MOO) au sein du constructeur.
ajouté l'auteur jsims281, source
"Malheureusement, vous rencontrerez régulièrement des programmeurs qui ignorent complètement tout cela. Cela fonctionne, c'est tout." Si triste mais si vrai. Je pense que je peux parler au nom de tous lorsque je dis que le seul fardeau substantiel dans notre vie professionnelle est, eh bien, ces personnes.
ajouté l'auteur Lightness Races in Orbit, source
@Samuel, merci pour les éloges. Oui, la réponse acceptée est la meilleure (objectif, bref ...); J'avais surtout besoin d'évacuer à ce jour et cette question était une opportunité délicieuse.
ajouté l'auteur AnoE, source
@JanDvorak, je voulais juste souligner le fait qu'il pourrait exister un comportement spécial "quelque part" dans un constructeur, mais vous avez raison, l'exemple que j'ai choisi était irréaliste. Enlevé.
ajouté l'auteur AnoE, source
L'analogie n'est pas si bonne. Si je vois cela, je m'attendrais à voir un commentaire quelque part sur la raison pour laquelle vous utilisez Bash pour faire le calcul, et selon votre raison, cela peut être parfaitement correct. La même chose ne fonctionnerait pas pour l'OP, car vous auriez essentiellement à mettre des excuses dans les commentaires partout où vous avez utilisé le constructeur "// Remarque: la construction de cet objet démarre une boucle d'événement qui ne retourne jamais".
ajouté l'auteur Brandin, source
J'aime beaucoup votre explication et j'aimerais marquer les deux réponses comme réponses (au moins un vote positif). L'analogie est bonne et votre piste de pensée et d'explication des coûts secondaires l'est aussi, mais je les considère comme des exigences auxiliaires pour le code (les mêmes que mes arguments). L’état actuel de la technique consiste à tester le code. La testabilité, etc. est donc une exigence de facto. Mais la déclaration de @ JörgWMittag du contrat fondamental du constructeur: construire quelque chose est parfaite.
ajouté l'auteur swe, source

Dans votre question, vous donnez suffisamment de raisons pour écarter cette approche, mais la question qui se pose est la suivante: "Y at-il quelque chose de plus manifestement mauvais ou sournois avec une telle approche?"

La première chose que j'ai faite ici, c'est que c'est inutile. Si votre constructeur ne finit jamais, aucune autre partie du programme ne peut obtenir une référence à l'objet construit . Quelle est donc la logique derrière son insertion dans un constructeur au lieu d'une méthode régulière? Je me suis alors rendu compte que la seule différence serait que, dans votre boucle, vous pouviez autoriser les références à ce partiellement construit qui s’échappait de la boucle. Bien que cela ne soit pas strictement limité à cette situation, il est garanti que si vous autorisez ce à s'échapper, ces références pointeront toujours vers un objet qui n'est pas entièrement construit.

Je ne sais pas si la sémantique autour de ce genre de situation est bien définie en C#, mais je dirais que cela n'a pas d'importance, car ce n'est pas quelque chose que la plupart des développeurs voudraient essayer de fouiller.

8
ajouté
@hoffmale Donc, les variables sont définies mais on ne sait pas quel effet cela pourrait avoir sur quelque chose comme GC. Pas tant que cela puisse être collecté, mais des choses plus ésotériques comme si l'objet peut être déplacé vers un emplacement différent sur le tas si le constructeur n'est pas terminé.
ajouté l'auteur JimmyJames, source
@mroman Pouvez-vous trouver une référence définitive pour le CLR qui montrerait que tel est le cas?
ajouté l'auteur JimmyJames, source
Je suppose que le constructeur est exécuté après la création de l'objet. Par conséquent, dans un langage sain qui laisse filtrer des données, ce comportement doit être parfaitement défini.
ajouté l'auteur rsn8887, source
Je suppose qu'en C ++, cela peut être fou, oui. Dans d'autres langues, les membres reçoivent une valeur par défaut avant que leurs valeurs réelles ne leur soient affectées. Par conséquent, même s'il est stupide d'écrire un tel code, le comportement est toujours bien défini. Si vous appelez des méthodes qui utilisent ces membres, vous pouvez vous heurter à NullPointerExceptions ou à des calculs incorrects (parce que les nombres sont définis par défaut à 0) ou à des choses du même genre, mais le résultat est techniquement déterministe.
ajouté l'auteur rsn8887, source
Eh bien, vous pouvez affecter des valeurs de membres avant l’exécution du constructeur afin que l’objet existe dans un état valide avec les valeurs par défaut attribuées aux membres ou les valeurs qui leur sont attribuées avant même l’exécution du constructeur. Vous n'avez pas besoin du constructeur pour initialiser les membres. Laissez-moi voir si je peux trouver cela quelque part dans la documentation.
ajouté l'auteur rsn8887, source
Je l'ai consultée, la section III.4.21 (pour la directive newobj) indique en effet que tous les champs membres seront instanciés à 0 , null ou à la valeur par défaut correspondante. Cela ne signifie toujours pas que cela sera attendu par toutes les variables membres (en particulier les références nulles aux champs d'une classe dérivée)
ajouté l'auteur Junji Shimagaki, source
@mroman: il pourrait être correct de divulguer this dans le constructeur - mais uniquement si vous êtes dans le constructeur de la classe la plus dérivée. Si vous avez deux classes, A et B avec B: A , si le constructeur de A aurait une fuite < code> this , les membres de B seraient toujours non initialisés (et les appels de méthodes virtuelles ou les conversions vers B peuvent causer des ravages en conséquence).
ajouté l'auteur Junji Shimagaki, source

+1 Pour le moins d’étonnement, mais c’est un concept difficile à expliquer aux nouveaux développeurs. À un niveau pragmatique, il est difficile de déboguer les exceptions générées dans les constructeurs. Si l’initialisation de l’objet échoue, il n’existera pas pour vous d’examiner l’état ou la journalisation depuis l’extérieur de ce constructeur.

Si vous ressentez le besoin de faire ce type de code, utilisez plutôt des méthodes statiques sur la classe.

Un constructeur existe pour fournir la logique d'initialisation lorsqu'un objet est instancié à partir d'une définition de classe. Vous construisez cette instance d'objet car il s'agit d'un conteneur qui encapsule un ensemble de propriétés et de fonctionnalités que vous souhaitez appeler à partir du reste de la logique de votre application.

Si vous n'avez pas l'intention d'utiliser l'objet que vous "construisez", alors à quoi sert-il d'instancier l'objet? Avoir une boucle while (true) dans un constructeur signifie effectivement que vous ne voulez jamais qu'il se termine ...

C # est un langage orienté objet très riche avec de nombreuses constructions et paradigmes différents que vous pouvez explorer, connaître vos outils et quand les utiliser, en bout de ligne:

En C#, n'exécutez pas de logique étendue ou infinie dans les constructeurs car ... il existe de meilleures alternatives

1
ajouté
sa ne semble pas offrir quoi que ce soit de substantiel sur les points soulevés et expliqués dans les 9 réponses précédentes
ajouté l'auteur gnat, source
Hé, je devais essayer :) Je pensais qu'il était important de souligner que bien qu'il n'y ait pas de règle contre cela, vous ne devriez pas en raison du fait qu'il existe des moyens plus simples. La plupart des autres réponses se concentrent sur les raisons techniques pour lesquelles c'est mauvais, mais c'est difficile à vendre à un nouveau dev qui dit "mais ça compile, donc je peux le laisser comme ça"
ajouté l'auteur Neal Gafter, source

Le but d'un constructeur est de bien construire votre objet. Avant que le constructeur ait terminé, il est en principe dangereux d'utiliser les méthodes de cet objet. Bien sûr, nous pouvons appeler des méthodes de l'objet dans le constructeur, mais chaque fois que nous le faisons, nous devons nous assurer que cela est valable pour l'objet dans son état actuel de demi-traitement.

En mettant indirectement toute la logique dans le constructeur, vous ajoutez plus de fardeau de ce type à vous-même. Cela suggère que vous n'avez pas assez bien conçu la différence entre initialisation et utilisation.

1
ajouté
cela ne semble rien offrir de substantiel par rapport aux points soulevés et expliqués dans les 8 réponses précédentes
ajouté l'auteur gnat, source

Pourquoi une boucle while (true) d'un constructeur est-elle réellement mauvaise?

C'est pas mal du tout.

Pourquoi (ou est-ce du tout?) considéré comme une mauvaise conception permettant à un constructeur de classe de démarrer une boucle sans fin (c'est-à-dire une boucle de jeu)?

Pourquoi voudriez-vous jamais faire cela? Sérieusement. Cette classe a une seule fonction: le constructeur. Si tout ce que vous voulez, c'est une fonction unique, c'est pourquoi nous avons des fonctions, pas des constructeurs.

Vous avez vous-même évoqué certaines des raisons évidentes, mais vous avez laissé de côté la plus importante:

There are better and simpler options to achieve the same thing.

S'il y a 2 choix et que l'un est meilleur, peu importe combien il vaut mieux, vous avez choisi le meilleur. Incidemment, c’est la raison pour laquelle nous ne n'utilisons presque jamais GoTo.

0
ajouté

Il n'y a rien de mauvais en soi. Cependant, l’important est la question de savoir pourquoi vous avez choisi d’utiliser cette construction. Qu'avez-vous obtenu en faisant cela?

Tout d’abord, je ne parlerai pas de l’utilisation de while (true) dans un constructeur. Absolument n’a rien de mal à utiliser cette syntaxe dans un constructeur. Cependant, le concept de "démarrage d'une boucle de jeu dans le constructeur" est susceptible de poser problème.

Dans ces situations, il est très courant que le constructeur construise simplement l'objet et dispose d'une fonction qui appelle la boucle infinie. Cela donne plus de flexibilité à l'utilisateur de votre code. Un exemple du type de problèmes pouvant apparaître est que vous limitez l'utilisation de votre objet. Si quelqu'un veut avoir un objet membre qui est "l'objet de jeu" dans sa classe, il doit être prêt à exécuter la boucle de jeu complète dans son constructeur car il doit construire l'objet. Cela pourrait conduire à un codage torturé pour contourner ce problème. Dans le cas général, vous ne pouvez pas pré-allouer votre objet de jeu, puis l'exécuter plus tard. Pour certains programmes, ce n'est pas un problème, mais il y a des classes de programmes dans lesquelles vous voulez pouvoir tout allouer à l'avance, puis appeler la boucle de jeu.

Une des règles de base de la programmation consiste à utiliser l'outil le plus simple pour le travail. Ce n'est pas une règle absolue, mais c'est une règle très utile. Si un appel de fonction aurait suffi, pourquoi construire un objet de classe? Si je vois un outil compliqué plus puissant utilisé, je suppose que le développeur avait une raison à cela, et je vais commencer à explorer les sortes de trucs étranges que vous pourriez faire.

Il y a des cas où cela pourrait être raisonnable. Il peut y avoir des cas où il est vraiment intuitif d’avoir une boucle de jeu dans le constructeur. Dans ces cas, vous courez avec! Par exemple, votre boucle de jeu peut être intégrée à un programme beaucoup plus vaste qui construit des classes pour incorporer certaines données. Si vous devez exécuter une boucle de jeu pour générer ces données, il peut être très raisonnable de l'exécuter dans un constructeur. Traitez cela simplement comme un cas spécial: ce serait valable parce que le programme global le rend intuitif pour le développeur suivant qui comprend ce que vous avez fait et pourquoi.

0
ajouté
Si la construction de votre objet nécessite une boucle de jeu, placez-la au moins dans une méthode factory. GameTestResults testResults = runGameTests (tests, configuration); plutôt que GameTestResults testResults = new GameTestResults (tests, configuration); .
ajouté l'auteur immibis, source
@immibis Oui, c'est vrai, sauf dans les cas où cela est déraisonnable. Si vous travaillez avec un système basé sur la réflexion qui instancie votre objet pour vous, vous n'avez peut-être pas la possibilité de créer une méthode fabrique.
ajouté l'auteur Cort Ammon, source

Votre objet me rappelle le début des tâches . L'objet de tâche a un constructeur, mais il existe également de nombreuses méthodes d'usine statiques, telles que: Task.Run , Task.Start et Task.Factory.StartNew. .

Celles-ci sont très similaires à ce que vous essayez de faire, et il est probable que ce soit la «convention». Le principal problème que les gens semblent avoir, c'est que cette utilisation d'un constructeur les surprend. @ JörgWMittag dit qu'il rompt un contrat fondamental , ce qui signifie que Jörg est très surpris. Je suis d'accord et n'ai plus rien à ajouter à cela.

Cependant, je veux suggérer que l'OP utilise des méthodes d'usine statiques. La surprise disparaît et il n'y a pas de contrat fondamental en jeu ici. Les gens sont habitués aux méthodes d'usine statiques qui font des choses spéciales, et elles peuvent être nommées en conséquence.

Vous pouvez fournir des constructeurs permettant un contrôle fin (comme le suggère @Brandin, quelque chose comme var g = nouveau jeu {...}; g.MainLoop (); ), qui s'adapte aux utilisateurs qui ne veulent pas pour commencer le jeu immédiatement, et peut-être que vous voulez le passer en premier lieu, et vous pouvez écrire quelque chose comme var runningGame = Game.StartNew (); pour le lancer immédiatement et facilement.

0
ajouté
Cela signifie que Jörg est fondamentalement surpris, c'est-à-dire qu'une telle surprise est une douleur dans le fond.
ajouté l'auteur TraumaPony, source

Mon impression est que certains concepts se sont mélangés et ont abouti à une question moins bien formulée.

Le constructeur d'une classe peut démarrer un nouveau thread qui ne se terminera jamais (bien que je préfère utiliser tant que (_KeepRunning) par rapport à tant que (true) , car je peux définir le membre booléen à faux quelque part).

Vous pouvez extraire cette méthode de création et de démarrage du fil dans une fonction qui lui est propre, afin de séparer la construction d’objet et le début de son travail. Je préfère cette méthode car je contrôle mieux l’accès aux ressources.

Vous pouvez également consulter le "Modèle d'objet actif". En fin de compte, je suppose que la question visait à savoir quand commencer le fil d'un "objet actif", et ma préférence va au découplage de la construction et au démarrage pour un meilleur contrôle.

0
ajouté