Je comprends que parfois vous avez besoin de propriétés, comme:
public int[] Transitions { get; set; }
ou:
[SerializeField] private int[] m_Transitions;
public int[] Transitions { get { return m_Transitions; } set { m_Transitions = value; } }
Mais j'ai l'impression que dans le développement de jeux, à moins que vous n'ayez une raison de faire autrement, tout devrait être presque aussi rapide que possible. Mon impression des choses que j'ai lues est que Unity 3D n'est pas sûr de l'accès en ligne via les propriétés, donc l'utilisation d'une propriété entraînera un appel de fonction supplémentaire.
Ai-je raison d'ignorer constamment les suggestions de Visual Studio de ne pas utiliser les champs publics? (Notez que nous utilisons int Foo { get; private set; }
là où il y a un avantage à montrer l'utilisation prévue.)
Je suis conscient que l'optimisation prématurée est mauvaise, mais comme je l'ai dit, dans le développement de jeux, vous ne ralentissez pas lorsqu'un effort similaire pourrait le rendre rapide.
ArrayList
). Cela est dû au fait que C n'a pas de génériques, et ces programmeurs C ont probablement trouvé plus facile de réimplémenter à plusieurs reprises des listes liées que de faire de même pourArrayLists
l'algorithme de redimensionnement de. Si vous trouvez une bonne optimisation de bas niveau, encapsulez-la!Réponses:
Pas nécessairement. Tout comme dans les logiciels d'application, il y a du code dans un jeu qui est critique pour les performances et du code qui ne l'est pas.
Si le code est exécuté plusieurs milliers de fois par trame, une telle optimisation de bas niveau peut avoir un sens. (Bien que vous souhaitiez d'abord vérifier s'il est réellement nécessaire de l'appeler aussi souvent. Le code le plus rapide est le code que vous n'exécutez pas)
Si le code est exécuté une fois toutes les deux secondes, la lisibilité et la maintenabilité sont bien plus importantes que les performances.
Avec des propriétés triviales dans le style de
int Foo { get; private set; }
vous pouvez être certain que le compilateur optimisera le wrapper de propriété. Mais:GetFoo()
/SetFoo(value)
: pour impliquer qu'il se passe beaucoup plus de choses en arrière-plan. (ou si je dois être encore plus sûr que d' autres obtiennent l'indice:CalculateFoo()
/SwitchFoo(value)
)la source
Update
entièrement vous fera gagner plus de temps que de faireUpdate
vite.En cas de doute, appliquez les meilleures pratiques.
Vous avez un doute.
C'était la réponse facile. La réalité est bien sûr plus complexe.
Tout d'abord, il y a le mythe selon lequel la programmation de jeux est une programmation ultra haute performance et tout doit être aussi rapide que possible. Je classe la programmation de jeux comme une programmation sensible aux performances . Le développeur doit être conscient des contraintes de performances et y travailler.
Deuxièmement, il y a le mythe selon lequel rendre chaque chose aussi rapide que possible est ce qui fait qu'un programme s'exécute rapidement. En réalité, identifier et optimiser les goulots d'étranglement est ce qui rend un programme rapide, et c'est beaucoup plus facile / moins cher / plus rapide à faire si le code est facile à maintenir et à modifier; le code extrêmement optimisé est difficile à maintenir et à modifier. Il s'ensuit que pour écrire des programmes extrêmement optimisés, vous devez utiliser une optimisation de code extrême aussi souvent que nécessaire et aussi rarement que possible.
Troisièmement, l'avantage des propriétés et des accesseurs est qu'ils sont robustes à changer et rendent ainsi le code plus facile à maintenir et à modifier - ce dont vous avez besoin pour écrire des programmes rapides. Vous souhaitez lever une exception chaque fois que quelqu'un souhaite définir votre valeur sur null? Utilisez un passeur. Vous souhaitez envoyer une notification chaque fois que la valeur change? Utilisez un passeur. Vous voulez enregistrer la nouvelle valeur? Utilisez un passeur. Vous voulez faire une initialisation paresseuse? Utilisez un getter.
Et quatrièmement, personnellement, je ne respecte pas les meilleures pratiques de Microsoft dans ce cas. Si j'ai besoin d'une propriété avec des getters et setters triviaux et publics , j'utilise simplement un champ, et je le change simplement en propriété quand j'en ai besoin. Cependant, j'ai très rarement besoin d'une propriété aussi triviale - il est beaucoup plus courant que je veuille vérifier les conditions aux limites, ou que je veuille rendre le setter privé.
la source
Il est très facile pour le runtime .net d'incorporer des propriétés simples, et c'est souvent le cas. Mais, cette inline est normalement désactivée par les profileurs, il est donc facile d'être induit en erreur et de penser que le changement d'une propriété publique en un domaine public accélérera le logiciel.
Dans le code qui est appelé par un autre code qui ne sera pas recompilé lorsque votre code est recompilé, il y a de bonnes raisons d'utiliser des propriétés, car le passage d'un champ à une propriété est un changement de rupture. Mais pour la plupart du code, outre la possibilité de définir un point d'arrêt sur le get / set, je n'ai jamais vu d'avantages à utiliser une propriété simple par rapport à un champ public.
La principale raison pour laquelle j'ai utilisé des propriétés au cours de mes 10 années en tant que programmeur C # professionnel était d'empêcher d'autres programmeurs de me dire que je ne respectais pas la norme de codage. C'était un bon compromis, car il n'y avait presque jamais de bonne raison de ne pas utiliser une propriété.
la source
Les structures et les champs nus faciliteront l'interopérabilité avec certaines API non gérées. Souvent, vous constaterez que l'API de bas niveau veut accéder aux valeurs par référence, ce qui est bon pour les performances (car nous évitons une copie inutile). L'utilisation de propriétés est un obstacle à cela, et souvent les bibliothèques d'encapsuleurs font des copies pour la facilité d'utilisation et parfois pour la sécurité.
De ce fait, vous obtiendrez souvent de meilleures performances avec des types de vecteurs et de matrices qui n'ont pas de propriétés mais des champs nus.
Les meilleures pratiques ne se créent pas sous vide. Malgré un certain culte du fret, en général, les meilleures pratiques sont là pour une bonne raison.
Dans ce cas, nous avons un couple:
Une propriété vous permet de modifier l'implémentation sans changer le code client (au niveau binaire, il est possible de changer un champ en une propriété sans changer le code client au niveau source, mais il se compilera en quelque chose de différent après le changement) . Cela signifie qu'en utilisant une propriété depuis le début, le code qui fait référence au vôtre n'aura pas à être recompilé juste pour changer ce que fait la propriété en interne.
Si toutes les valeurs possibles des champs de votre type ne sont pas des états valides, vous ne souhaitez pas les exposer au code client qui le modifiera. Ainsi, si certaines combinaisons de valeurs ne sont pas valides, vous souhaitez conserver les champs privés (ou internes).
J'ai dit le code client. Cela signifie un code qui appelle le vôtre. Si vous ne faites pas de bibliothèque (ou même si vous faites de la bibliothèque mais que vous utilisez interne au lieu de public), vous pouvez généralement vous en tirer avec une bonne discipline. Dans cette situation, la meilleure pratique d'utilisation des propriétés est de vous empêcher de vous tirer une balle dans le pied. De plus, il est beaucoup plus facile de raisonner sur le code si vous pouvez voir tous les endroits où un champ peut changer dans un seul fichier, au lieu d'avoir à vous soucier de tout ce qui est en train d'être modifié ailleurs. En fait, les propriétés sont également bonnes pour mettre des points d'arrêt lorsque vous déterminez ce qui ne va pas.
Oui, il est utile de voir ce qui se fait dans l'industrie. Cependant, avez-vous une motivation pour aller à l'encontre des meilleures pratiques? ou allez-vous simplement à l'encontre des meilleures pratiques - rendant le code plus difficile à raisonner - simplement parce que quelqu'un d'autre l'a fait? Ah, au fait, "les autres le font", c'est comme ça que vous démarrez un culte du cargo.
Alors ... Votre jeu tourne-t-il lentement? Il vaut mieux consacrer du temps à trouver le goulot d'étranglement et à le corriger, au lieu de spéculer sur ce que cela pourrait être. Vous pouvez être assuré que le compilateur fera beaucoup d'optimisations, à cause de cela, il est probable que vous cherchiez le mauvais problème.
D'un autre côté, si vous décidez quoi faire pour commencer, vous devez d'abord vous soucier des algorithmes et des structures de données, au lieu de vous soucier de petits détails tels que les champs par rapport aux propriétés.
Enfin, gagnez-vous quelque chose en allant à l'encontre des meilleures pratiques?
Pour les particularités de votre cas (Unity et Mono pour Android), Unity prend-il des valeurs par référence? Si ce n'est pas le cas, il copiera les valeurs de toute façon, aucun gain de performances là-bas.
Si c'est le cas, si vous transmettez ces données à une API qui prend ref. Est-il judicieux de rendre le champ public, ou vous pourriez rendre le type capable d'appeler directement l'API?
Oui, bien sûr, il pourrait y avoir des optimisations que vous pourriez faire en utilisant des structures avec des champs nus. Par exemple, vous y accédez avec des
pointeursSpan<T>
ou similaires. Ils sont également compacts en mémoire, ce qui les rend faciles à sérialiser pour envoyer sur le réseau ou à mettre en stockage permanent (et oui, ce sont des copies).Maintenant, avez-vous choisi les bons algorithmes et structures, s'ils se révèlent être un goulot d'étranglement, alors vous décidez quelle est la meilleure façon de le corriger ... qui pourrait être des structures avec des champs nus ou non. Vous pourrez vous en soucier si et quand cela se produit. Pendant ce temps, vous pouvez vous soucier de questions plus importantes telles que faire un bon jeu amusant.
la source
:
Vous avez raison de savoir qu'une optimisation prématurée est mauvaise, mais ce n'est que la moitié de l'histoire (voir la citation complète ci-dessous). Même dans le développement de jeux, tout ne doit pas être aussi rapide que possible.
Faites-le d'abord fonctionner. Alors seulement, optimisez les bits qui en ont besoin. Cela ne veut pas dire que le premier projet peut être désordonné ou inefficace, mais que l'optimisation n'est pas une priorité à ce stade.
" Le vrai problème est que les programmeurs ont passé beaucoup trop de temps à se soucier de l'efficacité aux mauvais endroits et aux mauvais moments . L' optimisation prématurée est à l'origine de tous les maux (ou du moins la plupart) de la programmation." Donald Knuth, 1974.
la source
Pour des trucs de base comme ça, l'optimiseur C # va bien faire les choses de toute façon. Si vous vous en souciez (et si vous vous en souciez pour plus de 1% de votre code, vous vous trompez ), un attribut a été créé pour vous. L'attribut
[MethodImpl(MethodImplOptions.AggressiveInlining)]
augmente considérablement les chances qu'une méthode soit alignée (les getters et setters de propriété sont des méthodes).la source
L'exposition de membres de type structure en tant que propriétés plutôt que champs entraînera souvent de mauvaises performances et une sémantique médiocre, en particulier dans les cas où les structures sont réellement traitées comme des structures plutôt que comme des "objets originaux".
Une structure en .NET est une collection de champs collés ensemble et qui, à certaines fins, peuvent être traités comme une unité. Le .NET Framework est conçu pour permettre aux structures d'être utilisées de la même manière que les objets de classe, et il peut parfois être pratique.
La plupart des conseils concernant les structures reposent sur l'idée que les programmeurs voudront utiliser les structures comme des objets, plutôt que comme des collections de champs collées. D'un autre côté, il existe de nombreux cas où il peut être utile d'avoir quelque chose qui se comporte comme un ensemble de champs collés. Si c'est ce dont on a besoin, les conseils .NET ajouteront du travail inutile aux programmeurs et aux compilateurs, ce qui donnera des performances et une sémantique moins bonnes que la simple utilisation directe de structures.
Les plus grands principes à garder à l'esprit lors de l'utilisation de structures sont:
Ne passez pas ou ne renvoyez pas les structures par valeur ou ne les faites pas copier inutilement.
Si le principe n ° 1 est respecté, les grandes structures fonctionneront aussi bien que les petites.
Si
Alphablob
est une structure contenant 26 publicsint
champs nommés az, et un getter de la propriétéab
qui retourne la somme de sesa
etb
champs, puis donnéAlphablob[] arr; List<Alphablob> list;
, le codeint foo = arr[0].ab + list[0].ab;
devra lire des champsa
etb
dearr[0]
, mais devra lire tous les 26 domaines de lalist[0]
même si elle ignorer tous sauf deux. Si l'on voulait avoir une collection de type liste générique qui pourrait fonctionner efficacement avec une structure commealphaBlob
, on devrait remplacer le getter indexé par une méthode:qui invoquerait alors
proc(ref backingArray[index], ref param);
. Compte tenu d'une telle collection, si l'on remplacesum = myCollection[0].ab;
parcela éviterait de devoir copier des parties de la
alphaBlob
qui ne seront pas utilisées dans le getter de propriété. Étant donné que le délégué passé n'accède à rien d'autre que sesref
paramètres, le compilateur peut passer un délégué statique.Malheureusement, le .NET Framework ne définit aucun des types de délégués nécessaires pour que cette chose fonctionne correctement, et la syntaxe finit par être un peu un gâchis. D'un autre côté, cette approche permet d'éviter de copier inutilement des structures, ce qui à son tour rendra pratique l'exécution d'actions sur place sur de grandes structures stockées dans des tableaux, ce qui est le moyen le plus efficace d'accéder au stockage.
la source
list
et le getter de propriété pourab
sont tous deux en ligne, il peut être possible pour un JITter de reconnaître que seuls les membresa
etb
sont nécessaires, mais je ne suis au courant d'aucune version de JITter faisant de telles choses.