C # est l'un des nombreux langages qui ciblent Microsoft Common Language Runtime (CLR). Les langages qui ciblent le CLR bénéficient de fonctionnalités telles que l'intégration multilingue et la gestion des exceptions, une sécurité améliorée, un modèle simplifié pour l'interaction des composants et des services de débogage et de profilage. Parmi les langages CLR actuels, C # est le plus utilisé pour les projets de développements qui ciblent les environnements de bureau, mobiles ou serveurs Windows.
C # est un langage fortement typé orienté objet. La vérification de type stricte en C #, à la fois au moment de la compilation et de l'exécution, entraîne le signalement de la majorité des erreurs de programmation C # typiques le plus tôt possible et leur emplacement est identifié assez précisément. Cela peut faire gagner beaucoup de temps C Programmation Sharp , par rapport à la recherche de la cause d'erreurs déroutantes qui peuvent survenir longtemps après que l'opération incriminée ait eu lieu dans des langues qui sont plus libérales dans leur application de la sécurité de type. Cependant, de nombreux codeurs C # jettent involontairement (ou négligemment) les avantages de cette détection, ce qui conduit à certains des problèmes abordés dans ce didacticiel C #.
Ce didacticiel décrit 10 des erreurs de programmation C # les plus courantes commises, ou des problèmes à éviter, par les programmeurs C # et leur fournit de l'aide.
Bien que la plupart des erreurs abordées dans cet article soient spécifiques à C #, certaines sont également pertinentes pour d'autres langages qui ciblent le CLR ou utilisent le Bibliothèque de classes Framework (FCL).
Les programmeurs de C ++ et de nombreux autres langages ont l'habitude de contrôler si les valeurs qu'ils attribuent aux variables sont simplement des valeurs ou des références à des objets existants. Dans la programmation C Sharp, cependant, cette décision est prise par le programmeur qui a écrit l'objet, et non par le programmeur qui instancie l'objet et l'assigne à une variable. C'est un «piège» courant pour ceux qui essaient d'apprendre la programmation C #.
Si vous ne savez pas si l'objet que vous utilisez est un type valeur ou un type référence, vous risquez de rencontrer des surprises. Par exemple:
Point point1 = new Point(20, 30); Point point2 = point1; point2.X = 50; Console.WriteLine(point1.X); // 20 (does this surprise you?) Console.WriteLine(point2.X); // 50 Pen pen1 = new Pen(Color.Black); Pen pen2 = pen1; pen2.Color = Color.Blue; Console.WriteLine(pen1.Color); // Blue (or does this surprise you?) Console.WriteLine(pen2.Color); // Blue
Comme vous pouvez le voir, les deux Point
et Pen
les objets ont été créés exactement de la même manière, mais la valeur de point1
est resté inchangé lorsqu'un nouveau X
la valeur de coordonnée a été affectée à point2
, tandis que la valeur de pen1
était modifié lorsqu'une nouvelle couleur a été attribuée à pen2
. On peut donc déduire que point1
et point2
chacun contient sa propre copie d'un Point
objet, alors que pen1
et pen2
contiennent des références au même Pen
objet. Mais comment pouvons-nous savoir cela sans faire cette expérience?
La réponse est de regarder les définitions des types d'objet (ce que vous pouvez facilement faire dans Visual Studio en plaçant votre curseur sur le nom du type d'objet et en appuyant sur F12):
public struct Point { ... } // defines a “value” type public class Pen { ... } // defines a “reference” type
Comme indiqué ci-dessus, dans la programmation C #, le struct
Le mot-clé est utilisé pour définir un type de valeur, tandis que le class
Le mot-clé est utilisé pour définir un type de référence. Pour ceux qui ont une formation C ++, qui ont été bercés dans un faux sentiment de sécurité par les nombreuses similitudes entre les mots-clés C ++ et C #, ce comportement est probablement une surprise qui peut vous amener à demander de l'aide à un didacticiel C #.
Si vous allez dépendre d'un comportement qui diffère entre les types valeur et référence - comme la possibilité de passer un objet en tant que paramètre de méthode et que cette méthode modifie l'état de l'objet - assurez-vous que vous avez affaire à la type d'objet correct pour éviter les problèmes de programmation C #.
En C #, les types valeur ne peuvent pas être null. Par définition, les types valeur ont une valeur, et même les variables non initialisées des types valeur doivent avoir une valeur. C'est ce qu'on appelle la valeur par défaut pour ce type. Cela conduit au résultat suivant, généralement inattendu lors de la vérification si une variable n'est pas initialisée:
class Program { static Point point1; static Pen pen1; static void Main(string[] args) { Console.WriteLine(pen1 == null); // True Console.WriteLine(point1 == null); // False (huh?) } }
Pourquoi pas point1
nul? La réponse est que Point
est un type de valeur et la valeur par défaut pour a Point
est (0,0), non nul. Ne pas reconnaître cela est une erreur très facile (et courante) à faire en C #.
De nombreux types de valeur (mais pas tous) ont un IsEmpty
propriété que vous pouvez vérifier pour voir si elle est égale à sa valeur par défaut:
Console.WriteLine(point1.IsEmpty); // True
Lorsque vous vérifiez si une variable a été initialisée ou non, assurez-vous de connaître la valeur par défaut d’une variable non initialisée de ce type et ne vous fiez pas à ce qu’elle soit nulle.
Il existe de nombreuses façons différentes de comparer des chaînes en C #.
Bien que de nombreux programmeurs utilisent le ==
opérateur pour la comparaison de chaînes, il s'agit en fait de l'un des moins méthodes souhaitables à utiliser, principalement parce qu’elles ne spécifient pas explicitement dans le code le type de comparaison souhaité.
Au contraire, la méthode préférée pour tester l'égalité des chaînes dans la programmation C # est d'utiliser Equals
méthode:
public bool Equals(string value); public bool Equals(string value, StringComparison comparisonType);
La première signature de méthode (c'est-à-dire sans le paramètre comparisonType
), est en fait identique à l'utilisation de ==
opérateur, mais présente l'avantage d'être explicitement appliqué aux chaînes. Il effectue une comparaison ordinale des chaînes, qui est essentiellement une comparaison octet par octet. Dans de nombreux cas, c'est exactement le type de comparaison que vous souhaitez, en particulier lorsque vous comparez des chaînes dont les valeurs sont définies par programme, telles que les noms de fichiers, les variables d'environnement, les attributs, etc. Dans ces cas, tant qu'une comparaison ordinale est effectivement du type correct de comparaison pour cette situation, le seul inconvénient de l'utilisation du Equals
méthode sans comparisonType
est que quelqu'un qui lit le code peut ne pas savoir quel type de comparaison vous faites.
Utilisation de Equals
signature de méthode qui inclut un comparisonType
Cependant, chaque fois que vous comparez des chaînes, non seulement votre code sera plus clair, cela vous fera également réfléchir explicitement au type de comparaison que vous devez faire. C'est une chose intéressante à faire, car même si l'anglais ne fournit pas beaucoup de différences entre les comparaisons ordinales et sensibles à la culture, d'autres langues en offrent beaucoup, et ignorer la possibilité d'autres langues vous ouvre à beaucoup de potentiel pour erreurs sur la route. Par exemple:
string s = 'strasse'; // outputs False: Console.WriteLine(s == 'straße'); Console.WriteLine(s.Equals('straße')); Console.WriteLine(s.Equals('straße', StringComparison.Ordinal)); Console.WriteLine(s.Equals('Straße', StringComparison.CurrentCulture)); Console.WriteLine(s.Equals('straße', StringComparison.OrdinalIgnoreCase)); // outputs True: Console.WriteLine(s.Equals('straße', StringComparison.CurrentCulture)); Console.WriteLine(s.Equals('Straße', StringComparison.CurrentCultureIgnoreCase));
La pratique la plus sûre est de toujours fournir un comparisonType
paramètre au Equals
méthode. Voici quelques directives de base:
CurrentCulture
ou CurrentCultureIgnoreCase
).Ordinal
ou OrdinalIgnoreCase
).InvariantCulture
et InvariantCultureIgnoreCase
ne doivent généralement pas être utilisés, sauf dans des circonstances très limitées, car les comparaisons ordinales sont plus efficaces. Si une comparaison tenant compte de la culture est nécessaire, elle doit généralement être effectuée par rapport à la culture actuelle ou à une autre culture spécifique.En plus de Equals
méthode, les chaînes fournissent également le Compare
, qui vous donne des informations sur l'ordre relatif des chaînes au lieu d'un simple test d'égalité. Cette méthode est préférable à <
, <=
, >
et >=
opérateurs, pour les mêmes raisons que celles décrites ci-dessus - pour éviter les problèmes C #.
Dans C # 3.0, l'ajout de Requête intégrée au langage (LINQ) à la langue a changé à jamais la façon dont les collections sont interrogées et manipulées. Depuis lors, si vous utilisez des instructions itératives pour manipuler des collections, vous n'avez pas utilisé LINQ alors que vous auriez probablement dû.
node js renvoie la valeur de la fonction
Certains programmeurs C # ne connaissent même pas l'existence de LINQ, mais heureusement, ce nombre est de plus en plus petit. Beaucoup pensent encore, cependant, qu'en raison de la similitude entre les mots clés LINQ et les instructions SQL, sa seule utilisation est dans le code qui interroge les bases de données.
Bien que l'interrogation de base de données soit une utilisation très répandue des instructions LINQ, elles fonctionnent en fait sur toute collection énumérable (c'est-à-dire, tout objet qui implémente l'interface IEnumerable). Ainsi, par exemple, si vous aviez un tableau de comptes, au lieu d'écrire une liste C # pour chacun:
decimal total = 0; foreach (Account account in myAccounts) { if (account.Status == 'active') { total += account.Balance; } }
vous pouvez simplement écrire:
decimal total = (from account in myAccounts where account.Status == 'active' select account.Balance).Sum();
Bien qu'il s'agisse d'un exemple assez simple de la façon d'éviter ce problème de programmation C # courant, il existe des cas où une seule instruction LINQ peut facilement remplacer des dizaines d'instructions dans une boucle itérative (ou des boucles imbriquées) dans votre code. Et moins de code général signifie moins de possibilités d'introduction de bogues. Gardez à l'esprit, cependant, qu'il peut y avoir un compromis en termes de performances. Dans les scénarios critiques en termes de performances, en particulier lorsque votre code itératif est capable de faire des hypothèses sur votre collection que LINQ ne peut pas, assurez-vous de faire une comparaison des performances entre les deux méthodes.
LINQ est idéal pour résumer la tâche de manipulation des collections, qu'il s'agisse d'objets en mémoire, de tables de base de données ou de documents XML. Dans un monde parfait, vous n’auriez pas besoin de savoir quels sont les objets sous-jacents. Mais l'erreur ici est de supposer que nous vivons dans un monde parfait. En fait, des instructions LINQ identiques peuvent renvoyer des résultats différents lorsqu'elles sont exécutées exactement sur les mêmes données, si ces données se trouvent être dans un format différent.
Par exemple, considérez la déclaration suivante:
decimal total = (from account in myAccounts where account.Status == 'active' select account.Balance).Sum();
Que se passe-t-il si l’un des objets account.Status
est égal à «Actif» (notez le A majuscule)? Eh bien, si myAccounts
était un DbSet
objet (qui a été configuré avec la configuration insensible à la casse par défaut), le where
expression correspondrait toujours à cet élément. Cependant, si myAccounts
était dans un tableau en mémoire, il ne correspondrait pas et produirait donc un résultat différent pour le total.
Mais attendez une minute. Lorsque nous avons parlé de comparaison de chaînes plus tôt, nous avons vu que le ==
L'opérateur a effectué une comparaison ordinale des chaînes. Alors pourquoi dans ce cas est le ==
opérateur effectuant une comparaison insensible à la casse?
La réponse est que lorsque les objets sous-jacents dans une instruction LINQ sont des références à des données de table SQL (comme c'est le cas avec l'objet Entity Framework DbSet dans cet exemple), l'instruction est convertie en une instruction T-SQL. Les opérateurs suivent alors les règles de programmation T-SQL, pas les règles de programmation C #, de sorte que la comparaison dans le cas ci-dessus finit par être insensible à la casse.
En général, même si LINQ est un moyen utile et cohérent d'interroger des collections d'objets, en réalité, vous devez toujours savoir si votre déclaration sera traduite en autre chose que C # sous le capot pour vous assurer que le comportement de votre code sera être comme prévu au moment de l’exécution.
Comme mentionné précédemment, les instructions LINQ fonctionnent sur tout objet qui implémente IEnumerable. Par exemple, la fonction simple suivante additionnera les soldes de toute collection de comptes:
public decimal SumAccounts(IEnumerable myAccounts) { return myAccounts.Sum(a => a.Balance); }
Dans le code ci-dessus, le type du paramètre myAccounts est déclaré comme IEnumerable
. Depuis myAccounts
référence a Sum
(C # utilise la «notation par points» familière pour référencer une méthode sur une classe ou une interface), nous nous attendons à voir une méthode appelée Sum()
sur la définition du IEnumerable
interface. Cependant, la définition de IEnumerable
, ne fait référence à aucun Sum
méthode et ressemble simplement à ceci:
public interface IEnumerable : IEnumerable { IEnumerator GetEnumerator(); }
Alors, où est le Sum()
méthode définie? C # est fortement typé, donc si la référence à Sum
n'était pas valide, le compilateur C # la marquerait certainement comme une erreur. On sait donc qu'il doit exister, mais où? De plus, où sont les définitions de toutes les autres méthodes fournies par LINQ pour interroger ou agréger ces collections?
La réponse est que Sum()
n'est pas une méthode définie sur le IEnumerable
interface. Il s'agit plutôt d'une méthode statique (appelée «méthode d'extension») qui est définie sur le System.Linq.Enumerable
classe:
namespace System.Linq { public static class Enumerable { ... // the reference here to “this IEnumerable source” is // the magic sauce that provides access to the extension method Sum public static decimal Sum(this IEnumerable source, Func selector); ... } }
Alors, qu'est-ce qui différencie une méthode d'extension de toute autre méthode statique et qu'est-ce qui nous permet d'y accéder dans d'autres classes?
La caractéristique distinctive d'une méthode d'extension est le this
modificateur sur son premier paramètre. C'est la «magie» qui l'identifie au compilateur comme une méthode d'extension. Le type du paramètre qu'il modifie (dans ce cas IEnumerable
) désigne la classe ou l'interface qui apparaîtra alors pour implémenter cette méthode.
(En complément, il n'y a rien de magique dans la similitude entre le nom de l'interface IEnumerable
et le nom de la classe Enumerable
sur laquelle la méthode d'extension est définie. Cette similitude n'est qu'un choix stylistique arbitraire .)
Avec cette compréhension, nous pouvons également voir que le sumAccounts
La fonction que nous avons introduite ci-dessus aurait pu être implémentée comme suit:
public decimal SumAccounts(IEnumerable myAccounts) { return Enumerable.Sum(myAccounts, a => a.Balance); }
Le fait que nous aurions pu l'implémenter de cette façon soulève à la place la question de savoir pourquoi avoir des méthodes d'extension? Méthodes d'extension sont essentiellement une commodité du langage de programmation C # qui vous permet d '«ajouter» des méthodes à des types existants sans créer un nouveau type dérivé, recompiler ou modifier le type d'origine.
Les méthodes d'extension sont introduites dans la portée en incluant un using [namespace];
déclaration en haut du fichier. Vous devez savoir quel espace de noms C # inclut les méthodes d'extension que vous recherchez, mais c'est assez facile à déterminer une fois que vous savez ce que vous recherchez.
Lorsque le compilateur C # rencontre un appel de méthode sur une instance d'un objet et ne trouve pas cette méthode définie sur la classe d'objet référencée, il examine ensuite toutes les méthodes d'extension qui sont dans la portée pour essayer d'en trouver une qui correspond à la méthode requise signature et classe. S'il en trouve une, il passera la référence d'instance comme premier argument à cette méthode d'extension, puis le reste des arguments, le cas échéant, sera passé en tant qu'arguments suivants à la méthode d'extension. (Si le compilateur C # ne trouve aucune méthode d'extension correspondante dans la portée, il générera une erreur.)
Les méthodes d'extension sont un exemple de «sucre syntaxique» de la part du compilateur C #, qui nous permet d'écrire du code qui est (généralement) plus clair et plus maintenable. Plus clair, c'est-à-dire si vous êtes au courant de leur utilisation. Sinon, cela peut être un peu déroutant, surtout au début.
Bien qu'il y ait certainement des avantages à utiliser les méthodes d'extension, elles peuvent causer des problèmes et un appel à l'aide à la programmation C # pour les développeurs qui ne les connaissent pas ou ne les comprennent pas correctement. Cela est particulièrement vrai lorsque vous regardez des exemples de code en ligne ou tout autre code pré-écrit. Lorsqu'un tel code produit des erreurs de compilation (car il appelle des méthodes qui ne sont clairement pas définies sur les classes sur lesquelles elles sont appelées), la tendance est de penser que le code s'applique à une version différente de la bibliothèque, ou à une bibliothèque complètement différente. Vous pouvez passer beaucoup de temps à rechercher une nouvelle version, ou une «bibliothèque manquante» fantôme, qui n’existe pas.
Même les développeurs qui sont familiers avec les méthodes d'extension se font toujours prendre à l'occasion, quand il y a une méthode avec le même nom sur l'objet, mais sa signature de méthode diffère d'une manière subtile de celle de la méthode d'extension. Beaucoup de temps peut être perdu à rechercher une faute de frappe ou une erreur qui n’est tout simplement pas là.
L'utilisation de méthodes d'extension dans les bibliothèques C # est de plus en plus répandue. En plus de LINQ, le Bloc d'application Unity et le Cadre d'API Web sont des exemples de deux bibliothèques modernes très utilisées par Microsoft qui utilisent également des méthodes d'extension, et il y en a beaucoup d'autres. Plus le framework est moderne, plus il est probable qu'il incorporera des méthodes d'extension.
Bien sûr, vous pouvez également écrire vos propres méthodes d'extension. Sachez cependant que si les méthodes d'extension semblent être invoquées comme les méthodes d'instance normales, ce n'est en réalité qu'une illusion. En particulier, vos méthodes d'extension ne peuvent pas référencer les membres privés ou protégés de la classe qu'elles étendent et ne peuvent donc pas remplacer complètement l'héritage de classe plus traditionnel.
C # fournit une grande variété d'objets de collection, les suivants n'étant qu'une liste partielle: Array
, ArrayList
, BitArray
, BitVector32
, Dictionary
, HashTable
, HybridDictionary
, List
, NameValueCollection
, OrderedDictionary
, Queue, Queue
, SortedList
, Stack, Stack
, StringCollection
, StringDictionary
.
Bien qu'il puisse y avoir des cas où trop de choix est aussi mauvais que pas assez de choix, ce n'est pas le cas avec les objets de collection. Le nombre d'options disponibles peut certainement fonctionner à votre avantage. Prenez un peu plus de temps à l'avance pour rechercher et choisir le type de collection optimal pour votre objectif. Cela se traduira probablement par de meilleures performances et moins de marge d'erreur.
S'il existe un type de collection spécifiquement ciblé sur le type d'élément que vous avez (tel qu'une chaîne ou un bit), préférez l'utiliser en premier. La mise en œuvre est généralement plus efficace lorsqu'elle cible un type d'élément spécifique.
Pour tirer parti de la sécurité de type de C #, vous devriez généralement préférer une interface générique à une interface non générique. Les éléments d'une interface générique sont du type que vous spécifiez lorsque vous déclarez votre objet, tandis que les éléments des interfaces non génériques sont de type object. Lorsque vous utilisez une interface non générique, le compilateur C # ne peut pas vérifier le type de votre code. De plus, lorsqu'il s'agit de collections de types de valeur primitifs, l'utilisation d'une collection non générique entraînera des répétitions boxe / déballage de ces types, ce qui peut avoir un impact négatif significatif sur les performances par rapport à une collection générique du type approprié.
Un autre problème courant de C # est d'écrire votre propre objet de collection. Cela ne veut pas dire que ce n’est jamais approprié, mais avec une sélection aussi complète que celle proposée par .NET, vous pouvez probablement gagner beaucoup de temps en utilisant ou en étendant celle qui existe déjà, plutôt que de réinventer la roue. En particulier, la bibliothèque de collections génériques C5 pour C # et CLI offre un large éventail de collections supplémentaires «prêtes à l'emploi», telles que des structures de données arborescentes persistantes, des files d'attente de priorité basées sur des tas, des listes de tableaux indexés par hachage, des listes liées et bien plus encore.
L’environnement CLR utilise un garbage collector, vous n’avez donc pas besoin de libérer explicitement la mémoire créée pour aucun objet. En fait, vous ne pouvez pas. Il n’y a pas d’équivalent du C ++ delete
ou l'opérateur free()
fonction en C. Mais cela ne signifie pas que vous pouvez simplement oublier tous les objets une fois que vous avez fini de les utiliser. De nombreux types d'objets encapsulent un autre type de ressource système (par exemple, un fichier disque, une connexion à une base de données, une socket réseau, etc.). Laisser ces ressources ouvertes peut rapidement épuiser le nombre total de ressources système, dégrader les performances et finalement entraîner des erreurs de programme.
Alors qu’une méthode destructrice peut être définie sur n’importe quelle classe C #, le problème avec les destructeurs (également appelés finaliseurs en C #) est que vous ne pouvez pas savoir avec certitude quand ils seront appelés. Ils sont appelés par le garbage collector (sur un thread séparé, ce qui peut entraîner des complications supplémentaires) à un moment indéterminé dans le futur. Essayer de contourner ces limitations en forçant le garbage collection avec GC.Collect()
n'est pas un Meilleures pratiques C # , car cela bloquera le thread pendant une durée inconnue pendant qu'il collecte tous les objets éligibles à la collecte.
Cela ne veut pas dire qu'il n'y a pas de bonnes utilisations pour les finaliseurs, mais libérer des ressources de manière déterministe n'en fait pas partie. Au contraire, lorsque vous utilisez une connexion à un fichier, un réseau ou une base de données, vous souhaitez libérer explicitement la ressource sous-jacente dès que vous en avez terminé.
Les fuites de ressources sont un problème dans presque tout environnement . Cependant, C # fournit un mécanisme robuste et simple à utiliser qui, s'il est utilisé, peut rendre les fuites beaucoup plus rares. Le framework .NET définit le IDisposable
interface, qui se compose uniquement de Dispose()
méthode. Tout objet qui implémente IDisposable
s'attend à ce que cette méthode soit appelée chaque fois que le consommateur de l'objet a fini de le manipuler. Il en résulte une libération explicite et déterministe des ressources.
Si vous créez et supprimez un objet dans le contexte d'un seul bloc de code, il est fondamentalement inexcusable d'oublier d'appeler Dispose()
, car C # fournit un using
déclaration qui assurera Dispose()
est appelé quel que soit le mode de sortie du bloc de code (qu'il s'agisse d'une exception, d'une instruction de retour ou simplement de la fermeture du bloc). Et oui, c’est pareil using
déclaration mentionnée précédemment qui est utilisée pour inclure les espaces de noms C # en haut de votre fichier. Il a un deuxième objectif, totalement indépendant, que de nombreux développeurs C # ignorent; à savoir, pour garantir que Dispose()
est appelé sur un objet lorsque le bloc de code est quitté:
using (FileStream myFile = File.OpenRead('foo.txt')) { myFile.Read(buffer, 0, 100); }
En créant un bloc using
dans l'exemple ci-dessus, vous savez avec certitude que myFile.Dispose()
sera appelé dès que vous en aurez fini avec le fichier, que Read()
lève une exception.
C # continue son application de la sécurité de type dans le runtime. Cela vous permet d'identifier de nombreux types d'erreurs en C # beaucoup plus rapidement que dans des langages tels que C ++, où des conversions de type erronées peuvent entraîner l'attribution de valeurs arbitraires aux champs d'un objet. Cependant, une fois de plus, les programmeurs peuvent gaspiller cette excellente fonctionnalité, ce qui entraîne des problèmes C #. Ils tombent dans ce piège parce que C # fournit deux façons différentes de faire les choses, l'une qui peut lever une exception et l'autre qui ne le fera pas. Certains hésiteront à utiliser la route d'exception, estimant que le fait de ne pas avoir à écrire un bloc try / catch leur évite un peu de codage.
Par exemple, voici deux façons différentes d'effectuer un cast de type explicite en C #:
// METHOD 1: // Throws an exception if account can't be cast to SavingsAccount SavingsAccount savingsAccount = (SavingsAccount)account; // METHOD 2: // Does NOT throw an exception if account can't be cast to // SavingsAccount; will just set savingsAccount to null instead SavingsAccount savingsAccount = account as SavingsAccount;
L'erreur la plus évidente qui pourrait se produire avec l'utilisation de la méthode 2 serait l'échec de la vérification de la valeur de retour. Cela entraînerait probablement une éventuelle NullReferenceException, qui pourrait éventuellement apparaître beaucoup plus tard, ce qui rendrait beaucoup plus difficile la recherche de la source du problème. En revanche, la méthode 1 aurait immédiatement jeté un InvalidCastException
rendant la source du problème beaucoup plus immédiatement évidente.
De plus, même si vous pensez à vérifier la valeur de retour dans la méthode 2, qu'allez-vous faire si vous la trouvez nulle? La méthode que vous écrivez est-elle un endroit approprié pour signaler une erreur? Y a-t-il autre chose que vous pouvez essayer si cette distribution échoue? Sinon, lever une exception est la bonne chose à faire, vous pouvez donc aussi bien la laisser se produire aussi près que possible de la source du problème.
Voici quelques exemples d'autres paires de méthodes courantes où l'une lève une exception et l'autre pas:
int.Parse(); // throws exception if argument can’t be parsed int.TryParse(); // returns a bool to denote whether parse succeeded IEnumerable.First(); // throws exception if sequence is empty IEnumerable.FirstOrDefault(); // returns null/default value if sequence is empty
Certains développeurs C # sont tellement 'opposés aux exceptions' qu'ils supposent automatiquement que la méthode qui ne lève pas d'exception est supérieure. Bien qu'il existe certains cas sélectionnés où cela peut être vrai, ce n'est pas du tout correct en tant que généralisation.
À titre d'exemple spécifique, dans un cas où vous avez une autre action légitime (par exemple, par défaut) à prendre si une exception aurait été générée, alors l'approche de non-exception pourrait être un choix légitime. Dans un tel cas, il peut en effet être préférable d'écrire quelque chose comme ceci:
if (int.TryParse(myString, out myInt)) { // use myInt } else { // use default value }
au lieu de:
try { myInt = int.Parse(myString); // use myInt } catch (FormatException) { // use default value }
Cependant, il est incorrect de supposer que TryParse
est donc nécessairement la «meilleure» méthode. Parfois c’est le cas, parfois non. C’est pourquoi il existe deux façons de procéder. Utilisez le bon pour le contexte dans lequel vous vous trouvez, en vous rappelant que les exceptions peuvent certainement être votre ami en tant que développeur.
Bien que ce problème ne soit certainement pas spécifique à C #, il est particulièrement flagrant dans la programmation C # car il abandonne les avantages de la vérification de type stricte offerte par le compilateur C #.
Les avertissements sont générés pour une raison. Alors que toutes les erreurs du compilateur C # signifient un défaut dans votre code, de nombreux avertissements le font également. Ce qui différencie les deux est que, dans le cas d'un avertissement, le compilateur n'a aucun problème à émettre les instructions que représente votre code. Même dans ce cas, il trouve votre code un peu louche et il y a une probabilité raisonnable que votre code ne reflète pas exactement votre intention.
la valorisation comparable au marché est basée sur le résultat net et le taux de capitalisation de la startup.
Un exemple simple courant pour le bien de ce didacticiel de programmation C # est lorsque vous modifiez votre algorithme pour éliminer l'utilisation d'une variable que vous utilisiez, mais que vous oubliez de supprimer la déclaration de variable. Le programme fonctionnera parfaitement, mais le compilateur marquera la déclaration de variable inutile. Le fait que le programme fonctionne parfaitement fait que les programmeurs négligent de corriger la cause de l'avertissement. De plus, les codeurs profitent d'une fonctionnalité de Visual Studio qui leur permet de masquer facilement les avertissements dans la fenêtre «Liste d'erreurs» afin qu'ils puissent se concentrer uniquement sur les erreurs. Il ne faut pas longtemps pour qu'il y ait des dizaines d'avertissements, tous parfaitement ignorés (ou pire encore, cachés).
Mais si vous ignorez ce type d'avertissement, tôt ou tard, quelque chose comme celui-ci peut très bien se retrouver dans votre code:
class Account { int myId; int Id; // compiler warned you about this, but you didn’t listen! // Constructor Account(int id) { this.myId = Id; // OOPS! } }
Et à la vitesse qu'Intellisense nous permet d'écrire du code, cette erreur n'est pas aussi improbable qu'elle en a l'air.
Vous avez maintenant une erreur sérieuse dans votre programme (bien que le compilateur ne l'ait signalé que comme un avertissement, pour les raisons déjà expliquées), et selon la complexité de votre programme, vous pourriez perdre beaucoup de temps à suivre celui-ci. Si vous aviez prêté attention à cet avertissement en premier lieu, vous auriez évité ce problème avec une solution simple de cinq secondes.
N'oubliez pas que le compilateur C Sharp vous donne de nombreuses informations utiles sur la robustesse de votre code… si vous écoutez. N'ignorez pas les avertissements. Ils ne prennent généralement que quelques secondes à réparer, et en réparer de nouveaux lorsqu'ils surviennent peut vous faire gagner des heures. Entraînez-vous à vous attendre à ce que la fenêtre «Liste d'erreurs» de Visual Studio affiche «0 erreurs, 0 avertissements», de sorte que tout avertissement vous rende suffisamment mal à l'aise pour y remédier immédiatement.
Bien sûr, il existe des exceptions à chaque règle. En conséquence, il peut y avoir des moments où votre code semblera un peu louche pour le compilateur, même si c'est exactement ce que vous vouliez. Dans ces très rares cas, utilisez #pragma warning disable [warning id]
autour uniquement du code qui déclenche l'avertissement, et uniquement pour l'ID d'avertissement qu'il déclenche. Cela supprimera cet avertissement, et cet avertissement uniquement, afin que vous puissiez toujours rester vigilant pour les nouveaux.
C # est un langage puissant et flexible avec de nombreux mécanismes et paradigmes qui peuvent considérablement améliorer la productivité. Comme pour tout outil logiciel ou langage, cependant, avoir une compréhension ou une appréciation limitée de ses capacités peut parfois être plus un obstacle qu'un avantage, laissant une personne dans l'état proverbial de «savoir suffisamment pour être dangereux».
Utiliser un tutoriel C Sharp comme celui-ci pour se familiariser avec les nuances clés de C #, telles que (mais en aucun cas limitées à) les problèmes soulevés dans cet article, aidera à l'optimisation C # tout en évitant certains de ses pièges les plus courants du langage.
En relation: 6 questions d'entrevue essentielles sur C #C # est l'un des nombreux langages de programmation qui ciblent Microsoft CLR, ce qui lui donne automatiquement les avantages d'une intégration et d'une gestion des exceptions inter-langues, d'une sécurité améliorée, d'un modèle simplifié d'interaction avec les composants et de services de débogage et de profilage.
C ++ et C # sont deux langages complètement différents, malgré des similitudes de nom et de syntaxe. C # a été conçu pour être un peu plus haut niveau que C ++, et les deux langages adoptent également des approches différentes des détails comme qui détermine si un paramètre est passé par référence ou par valeur.
C # est utilisé pour de nombreuses raisons, mais les avantages de l'ensemble d'outils Microsoft CLR et d'une large communauté de développeurs sont deux principaux atouts du langage.