Je suis un développeur C ++ expérimenté, je connais le langage dans les moindres détails et j'ai utilisé intensivement certaines de ses fonctionnalités spécifiques. De plus, je connais les principes d'OOD et les modèles de conception. J'apprends maintenant le C # mais je ne peux pas arrêter le sentiment de ne pas pouvoir me débarrasser de l'état d'esprit C ++. Je me suis tellement attaché aux forces du C ++ que je ne peux pas vivre sans certaines fonctionnalités. Et je ne trouve pas de bonnes solutions de contournement ou de remplacement pour eux en C #.
Quelles bonnes pratiques , modèles de conception , idiomes différents en C # du point de vue C ++ pouvez-vous suggérer? Comment obtenir un design C ++ parfait qui ne semble pas stupide en C #?
Plus précisément, je ne trouve pas de bonne façon de traiter en C # (exemples récents):
- Contrôle de la durée de vie des ressources qui nécessitent un nettoyage déterministe (comme les fichiers). C'est facile à avoir
using
en main mais comment l'utiliser correctement lorsque la propriété de la ressource est transférée [... entre les threads]? En C ++, j'utilisais simplement des pointeurs partagés et je le laissais prendre en charge la «collecte des ordures» juste au bon moment. - Lutte constante avec des fonctions prioritaires pour des génériques spécifiques (j'aime des choses comme la spécialisation partielle de modèles en C ++). Dois-je simplement abandonner toute tentative de faire une programmation générique en C #? Peut-être que les génériques sont limités à dessein et que ce n'est pas C -ish pour les utiliser sauf pour un domaine spécifique de problèmes?
- Fonctionnalité de type macro. Bien qu'il s'agisse généralement d'une mauvaise idée, pour certains domaines de problèmes, il n'y a pas d'autre solution de contournement (par exemple, l'évaluation conditionnelle d'une instruction, comme avec les journaux qui ne devraient aller qu'aux versions de débogage). Ne pas les avoir signifie que je dois mettre plus de passe-
if (condition) {...}
partout et ce n'est toujours pas égal en termes de déclenchement d'effets secondaires.
C#
pour générer un autre code. Vous pouvez lire unCSV
ou unXML
ou ce que vous avez en entrée et générer unC#
ou unSQL
fichier. Cela peut être plus puissant que l'utilisation de macros fonctionnelles.Réponses:
D'après mon expérience, il n'y a pas de livre pour le faire. Les forums aident avec les idiomes et les meilleures pratiques, mais à la fin, vous devrez mettre du temps. Entraînez-vous en résolvant un certain nombre de problèmes différents en C #. Regardez ce qui était difficile / maladroit et essayez de les rendre moins difficiles et moins maladroits la prochaine fois. Pour moi, ce processus a pris environ un mois avant que je ne le comprenne.
La plus grande chose sur laquelle se concentrer est le manque de gestion de la mémoire. En C ++, cela domine chaque décision de conception que vous prenez, mais en C #, c'est contre-productif. En C #, vous voulez souvent ignorer la mort ou d'autres transitions d'état de vos objets. Ce type d'association ajoute un couplage inutile.
Surtout, concentrez-vous sur l'utilisation des nouveaux outils le plus efficacement possible, ne vous battez pas contre un attachement aux anciens.
En réalisant que différents idiomes poussent vers différentes approches de conception. Vous ne pouvez pas simplement porter quelque chose de 1: 1 entre (la plupart) des langues et vous attendre à quelque chose de beau et d'idiomatique à l'autre bout.
Mais en réponse à des scénarios spécifiques:
Ressources non gérées partagées - C'était une mauvaise idée en C ++, et c'est tout autant une mauvaise idée en C #. Si vous avez un fichier, ouvrez-le, utilisez-le et fermez-le. Le partager entre des threads demande simplement des problèmes, et si un seul thread l'utilise vraiment, alors ouvrez-le / possédez / fermez-le. Si vous avez vraiment un fichier à longue durée de vie pour une raison quelconque, créez une classe pour le représenter et laissez l'application gérer cela selon les besoins (fermer avant de quitter, lier près au type d'événements de fermeture d'application, etc.)
Spécialisation partielle de modèle - Vous n'avez pas de chance ici, bien que plus j'utilise C #, plus je trouve que l'utilisation de modèle que j'ai faite en C ++ tombe dans YAGNI. Pourquoi faire quelque chose de générique quand T est toujours un entier? D'autres scénarios sont mieux résolus en C # par une application libérale des interfaces. Vous n'avez pas besoin de génériques lorsqu'un type d'interface ou de délégué peut fortement taper ce que vous voulez faire varier.
Conditionnelles du préprocesseur - C # has
#if
, go noix. Mieux encore, il possède des attributs conditionnels qui supprimeront simplement cette fonction du code si un symbole de compilateur n'est pas défini.la source
Pour autant que je puisse voir, la seule chose permettant une gestion déterministe de la durée de vie est
IDisposable
, qui tombe en panne (comme dans: pas de support de système de langue ou de type) quand, comme vous l'avez remarqué, vous devez transférer la propriété d'une telle ressource.Être dans le même bateau que vous - développeur C ++ faisant C # atm. - le manque de support pour modéliser le transfert de propriété (fichiers, connexions db, ...) dans les types / signatures C # a été très ennuyeux pour moi, mais je dois admettre que cela fonctionne assez bien pour "juste" s'appuyer sur les documents convention et API pour bien faire les choses.
IDisposable
pour effectuer un nettoyage déterministe si nécessaire.IDisposable
, assurez-vous simplement que l'API impliquée est claire à ce sujet et ne l'utilisez pasusing
dans ce cas, car elleusing
ne peut pas être "annulée" pour ainsi dire.Oui, ce n'est pas aussi élégant que ce que vous pouvez exprimer dans le système de type avec C ++ moderne (déplacer la sémantique, etc.), mais c'est le travail, et (étonnamment pour moi) la communauté C # dans son ensemble ne semble pas soins trop nombreux, AFAICT.
la source
Vous avez mentionné deux fonctionnalités C ++ spécifiques. Je vais discuter de RAII:
Il n'est généralement pas applicable, car C # est un ramasse-miettes. La construction C # la plus proche est probablement l'
IDisposable
interface, utilisée comme suit:Vous ne devriez pas vous attendre à l'implémenter
IDisposable
souvent (et cela est légèrement avancé), car ce n'est pas nécessaire pour les ressources gérées. Cependant, vous devez utiliser lausing
construction (ouDispose
vous appeler , si celausing
est impossible) pour tout ce qui est implémentéIDisposable
. Même si vous vous trompez, des classes C # bien conçues essaieront de gérer cela pour vous (en utilisant les finaliseurs). Cependant, dans un langage récupéré, le modèle RAII ne fonctionne pas complètement (car la collecte n'est pas déterministe), donc le nettoyage de vos ressources sera retardé (parfois de manière catastrophique).la source
Disposing()
du fichier et devrait probablement être utiliséusing
.C'est une très bonne question, car j'ai vu un certain nombre de développeurs C ++ écrire un terrible code C #.
Il vaut mieux ne pas penser à C # comme "C ++ avec une syntaxe plus agréable". Ce sont des langues différentes qui nécessitent des approches différentes dans votre réflexion. C ++ vous oblige à penser constamment à ce que le CPU et la mémoire feront. C # n'est pas comme ça. C # est spécifiquement conçu pour que vous ne pensiez pas au processeur et à la mémoire, mais que vous pensiez plutôt au domaine commercial pour lequel vous écrivez .
Un exemple de cela que j'ai vu est que beaucoup de développeurs C ++ aimeront utiliser pour les boucles car ils sont plus rapides que foreach. C'est généralement une mauvaise idée en C # car cela restreint les types possibles de la collection sur laquelle itérer (et donc la réutilisation et la flexibilité du code).
Je pense que la meilleure façon de réajuster du C ++ au C # est d'essayer d'aborder le codage sous un angle différent. Au début, cela sera difficile, car au fil des ans, vous serez complètement habitué à utiliser le fil "que font le CPU et la mémoire" dans votre cerveau pour filtrer le code que vous écrivez. Mais en C #, vous devriez plutôt penser aux relations entre les objets dans le domaine métier. "Qu'est-ce que je veux faire" par opposition à "que fait l'ordinateur".
Si vous voulez faire quelque chose pour tout dans une liste d'objets, au lieu d'écrire une boucle for sur la liste, créez une méthode qui prend an
IEnumerable<MyObject>
et utilisez uneforeach
boucle.Vos exemples spécifiques:
Vous ne devriez jamais faire cela en C # (en particulier entre les threads). Si vous devez faire quelque chose dans un fichier, faites-le au même endroit à la fois. Il est correct (et même une bonne pratique) d'écrire une classe wrapper qui gère la ressource non managée qui est transmise entre vos classes, mais n'essayez pas de passer un fichier entre les threads et demandez à des classes distinctes d'écrire / lire / fermer / ouvrir. Ne partagez pas la propriété des ressources non gérées. Utilisez le
Dispose
modèle pour gérer le nettoyage.Les génériques en C # sont conçus pour être génériques. Les spécialisations des génériques devraient être gérées par des classes dérivées. Pourquoi un
List<T>
comportement différent devrait-il se produireList<int>
ou d'unList<string>
? Toutes les opérations surList<T>
sont génériques pour s'appliquer à toutList<T>
. Si vous souhaitez modifier le comportement de la.Add
méthode sur aList<string>
, créez une classe dérivéeMySpecializedStringCollection : List<string>
ou une classe composéeMySpecializedStringCollection : IList<string>
qui utilise le générique en interne mais fait les choses de manière différente. Cela vous aide à éviter de violer le principe de substitution de Liskov et de visser royalement les autres qui utilisent votre classe.Comme d'autres l'ont dit, vous pouvez utiliser des commandes de préprocesseur pour ce faire. Les attributs sont encore meilleurs. En général, les attributs sont le meilleur moyen de gérer des choses qui ne sont pas des fonctionnalités "essentielles" pour une classe.
En bref, lors de l'écriture de C #, gardez à l'esprit que vous devez penser entièrement au domaine de l'entreprise et non au processeur et à la mémoire. Vous pouvez optimiser plus tard si nécessaire , mais votre code doit refléter les relations entre les principes commerciaux que vous essayez de mapper.
la source
IDisposable
( pas la ressource brute) d'une classe à une autre: il suffit de voir l' API StreamWriter où cela a un sens parfait pourDispose()
le flux passé. Et il y a le trou dans le langage C # - vous ne pouvez pas l'exprimer dans le système de type.La chose la plus différente pour moi était la tendance à tout renouveler . Si vous voulez un objet, oubliez d'essayer de le passer à une routine et de partager les données sous-jacentes, il semble que le modèle C # soit juste pour créer vous-même un nouvel objet. Cela, en général, semble être beaucoup plus de la manière C #. Au début, ce modèle semblait très inefficace, mais je suppose que la création de la plupart des objets en C # est une opération rapide.
Cependant, il y a aussi les classes statiques qui m'ennuient vraiment car de temps en temps vous essayez d'en créer une pour découvrir que vous n'avez qu'à l'utiliser. Je trouve que ce n'est pas si facile de savoir lequel utiliser.
Je suis assez heureux dans un programme C # de simplement l'ignorer quand je n'en ai plus besoin, mais rappelez-vous juste ce que cet objet contient - en général, vous n'avez qu'à penser s'il contient des données `` inhabituelles '', les objets qui ne sont constitués que de mémoire le feront partez tout seuls, les autres devront être éliminés ou vous devrez voir comment les détruire - manuellement ou à l'aide d'un bloc d'utilisation sinon.
vous le récupérerez très rapidement, C # n'est guère différent de VB, vous pouvez donc le traiter comme le même outil RAD qu'il est (si cela aide à penser à C # comme VB.NET, faites-le, la syntaxe n'est que légèrement différent, et mes vieux jours d'utilisation de VB m'ont mis dans la bonne mentalité pour le développement RAD). La seule difficulté est de déterminer quelle partie des énormes bibliothèques .net vous devez utiliser. Google est certainement votre ami ici!
la source