Si je crée un booléen dans ma classe, juste quelque chose comme bool check
, il est par défaut faux.
Quand je crée le même booléen dans ma méthode, bool check
(au lieu de dans la classe), j'obtiens une erreur "utilisation de la vérification de variable locale non assignée". Pourquoi?
c#
language-design
local-variables
nachime
la source
la source
Réponses:
Les réponses de Yuval et David sont fondamentalement correctes; résumer:
Un commentateur de la réponse de David demande pourquoi il est impossible de détecter l'utilisation d'un champ non attribué via une analyse statique; c'est le point que je souhaite développer dans cette réponse.
Tout d'abord, pour toute variable, locale ou non, il est en pratique impossible de déterminer exactement si une variable est affectée ou non. Considérer:
La question "x est-il attribué?" équivaut à "M () renvoie-t-il vrai?" Maintenant, supposons que M () renvoie vrai si le dernier théorème de Fermat est vrai pour tous les entiers inférieurs à onze gajillion, et faux sinon. Afin de déterminer si x est définitivement assigné, le compilateur doit essentiellement produire une preuve du dernier théorème de Fermat. Le compilateur n'est pas si intelligent.
Donc, ce que le compilateur fait à la place pour les locaux, c'est implémente un algorithme qui est rapide , et surestime lorsqu'un local n'est pas définitivement affecté. Autrement dit, il a quelques faux positifs, où il dit "Je ne peux pas prouver que ce local est attribué" même si vous et moi savons que c'est le cas. Par exemple:
Supposons que N () renvoie un entier. Vous et moi savons que N () * 0 sera égal à 0, mais le compilateur ne le sait pas. (Remarque: le compilateur C # 2.0 fait savoir que, mais je retirai que l' optimisation, comme la spécification ne dit que le compilateur sait.)
Très bien, alors que savons-nous jusqu'à présent? Il n'est pas pratique pour les locaux d'obtenir une réponse exacte, mais nous pouvons surestimer la non-attribution à bon marché et obtenir un résultat assez bon qui se trompe du côté de "vous faire réparer votre programme peu clair". C'est bon. Pourquoi ne pas faire la même chose pour les champs? Autrement dit, créer un vérificateur d'affectation précis qui surestime à moindre coût?
Eh bien, de combien de façons existe-t-il pour qu'un local soit initialisé? Il peut être attribué dans le texte de la méthode. Il peut être attribué dans un lambda dans le texte de la méthode; que lambda pourrait ne jamais être invoqué, donc ces affectations ne sont pas pertinentes. Ou il peut être passé comme "out" à une autre méthode, à quel point nous pouvons supposer qu'il est assigné lorsque la méthode retourne normalement. Ce sont des points très clairs auxquels le local est attribué, et ils sont exactement là dans la même méthode que le local est déclaré . La détermination de l'attribution définitive des locaux ne nécessite qu'une analyse locale . Les méthodes ont tendance à être courtes - bien moins d'un million de lignes de code dans une méthode - et l'analyse de l'ensemble de la méthode est donc assez rapide.
Maintenant qu'en est-il des champs? Les champs peuvent être initialisés dans un constructeur bien sûr. Ou un initialiseur de champ. Ou le constructeur peut appeler une méthode d'instance qui initialise les champs. Ou le constructeur peut appeler une méthode virtuelle qui initialise les champs. Ou le constructeur peut appeler une méthode dans une autre classe , qui pourrait être dans une bibliothèque , qui initialise les champs. Les champs statiques peuvent être initialisés dans les constructeurs statiques. Les champs statiques peuvent être initialisés par d' autres constructeurs statiques.
Essentiellement, l'initialiseur d'un champ peut être n'importe où dans le programme entier , y compris à l'intérieur des méthodes virtuelles qui seront déclarées dans des bibliothèques qui n'ont pas encore été écrites :
Est-ce une erreur de compiler cette bibliothèque? Si oui, comment BarCorp est-il censé corriger le bogue? En attribuant une valeur par défaut à x? Mais c'est déjà ce que fait le compilateur.
Supposons que cette bibliothèque soit légale. Si FooCorp écrit
est- ce une erreur? Comment le compilateur est-il censé comprendre cela? Le seul moyen est de faire une analyse complète du programme qui suit la statique d'initialisation de chaque champ sur chaque chemin possible à travers le programme , y compris les chemins qui impliquent le choix de méthodes virtuelles au moment de l'exécution . Ce problème peut être arbitrairement difficile ; cela peut impliquer l'exécution simulée de millions de chemins de contrôle. L'analyse des flux de contrôle locaux prend quelques microsecondes et dépend de la taille de la méthode. L'analyse des flux de contrôle globaux peut prendre des heures car elle dépend de la complexité de chaque méthode du programme et de toutes les bibliothèques .
Alors pourquoi ne pas faire une analyse moins chère qui n'a pas à analyser l'ensemble du programme, et qui surestime simplement encore plus sévèrement? Eh bien, proposez un algorithme qui fonctionne qui ne rend pas trop difficile l'écriture d'un programme correct qui compile réellement, et l'équipe de conception peut l'envisager. Je ne connais aucun algorithme de ce type.
Maintenant, le commentateur suggère "d'exiger qu'un constructeur initialise tous les champs". Ce n'est pas une mauvaise idée. En fait, c'est une si bonne idée que C # a déjà cette fonctionnalité pour les structures . Un constructeur struct est requis pour affecter définitivement tous les champs au moment où le ctor retourne normalement; le constructeur par défaut initialise tous les champs à leurs valeurs par défaut.
Et les cours? Eh bien, comment savez-vous qu'un constructeur a initialisé un champ ? Le ctor pourrait appeler une méthode virtuelle pour initialiser les champs, et maintenant nous sommes de retour dans la même position que nous étions auparavant. Les structures n'ont pas de classes dérivées; les classes pourraient. Une bibliothèque contenant une classe abstraite doit-elle contenir un constructeur qui initialise tous ses champs? Comment la classe abstraite sait-elle à quelles valeurs les champs doivent être initialisés?
John suggère simplement d'interdire les méthodes d'appel dans un ctor avant que les champs ne soient initialisés. Donc, pour résumer, nos options sont:
L'équipe de conception a choisi la troisième option.
la source
bool x;
équivalentbool x = false;
même à l'intérieur d'une méthode ?Parce que le compilateur essaie de vous empêcher de faire une erreur.
L'initialisation de votre variable
false
change-t-elle quelque chose dans ce chemin d'exécution particulier? Probablement pas, considérerdefault(bool)
est faux de toute façon, mais cela vous oblige à être conscient que cela se produit. L'environnement .NET vous empêche d'accéder à la «mémoire des déchets», car il initialisera toute valeur par défaut. Mais quand même, imaginez qu'il s'agissait d'un type de référence et que vous passiez une valeur non initialisée (null) à une méthode qui attend une valeur non nulle et que vous obteniez un NRE au moment de l'exécution. Le compilateur essaie simplement d'empêcher cela, en acceptant le fait que cela peut parfois entraîner desbool b = false
déclarations.Eric Lippert en parle dans un article de blog :
Pourquoi cela ne s'applique-t-il pas à un champ de classe? Eh bien, je suppose que la ligne a dû être tracée quelque part, et l'initialisation des variables locales est beaucoup plus facile à diagnostiquer et à obtenir correctement, par opposition aux champs de classe. Le compilateur pourrait le faire, mais pensez à toutes les vérifications possibles qu'il aurait besoin de faire (où certaines d'entre elles sont indépendantes du code de classe lui-même) afin d'évaluer si chaque champ d'une classe est initialisé. Je ne suis pas un concepteur de compilateurs, mais je suis sûr que ce serait certainement plus difficile car il y a beaucoup de cas qui sont pris en compte et qui doivent également être effectués en temps opportun . Pour chaque fonctionnalité que vous devez concevoir, écrire, tester et déployer, la valeur de sa mise en œuvre par rapport à l'effort déployé serait inutile et compliquée.
la source
La réponse courte est que le code accédant à des variables locales non initialisées peut être détecté par le compilateur de manière fiable, en utilisant une analyse statique. Alors que ce n'est pas le cas des champs. Le compilateur applique donc le premier cas, mais pas le second.
Ce n'est rien de plus qu'une décision de conception du langage C #, comme l' explique Eric Lippert . Le CLR et l'environnement .NET n'en ont pas besoin. VB.NET, par exemple, compilera très bien avec des variables locales non initialisées, et en réalité le CLR initialise toutes les variables non initialisées aux valeurs par défaut.
La même chose pourrait se produire avec C #, mais les concepteurs du langage ont choisi de ne pas le faire. La raison en est que les variables initialisées sont une énorme source de bogues et ainsi, en imposant l'initialisation, le compilateur aide à réduire les erreurs accidentelles.
Alors pourquoi cette initialisation explicite obligatoire ne se produit-elle pas avec des champs dans une classe? Tout simplement parce que cette initialisation explicite pourrait se produire lors de la construction, par le biais d'une propriété appelée par un initialiseur d'objet, ou même par une méthode appelée longtemps après l'événement. Le compilateur ne peut pas utiliser l'analyse statique pour déterminer si chaque chemin possible à travers le code conduit à l'initialisation explicite de la variable avant nous. Se tromper serait ennuyeux, car le développeur pourrait se retrouver avec un code valide qui ne se compilera pas. Donc C # ne l'applique pas du tout et le CLR est laissé pour initialiser automatiquement les champs à une valeur par défaut s'il n'est pas défini explicitement.
L'application de l'initialisation des variables locales par C # est limitée, ce qui surprend souvent les développeurs. Considérez les quatre lignes de code suivantes:
La deuxième ligne de code ne se compilera pas, car elle essaie de lire une variable de chaîne non initialisée. La quatrième ligne de code se compile très bien cependant, comme cela
array
a été initialisé, mais uniquement avec les valeurs par défaut. La valeur par défaut d'une chaîne étant nulle, nous obtenons une exception au moment de l'exécution. Quiconque a passé du temps ici sur Stack Overflow saura que cette incohérence d'initialisation explicite / implicite conduit à un grand nombre d'erreur "Pourquoi est-ce que j'obtiens une erreur" Référence d'objet non définie à une instance d'un objet "?" des questions.la source
public interface I1 { string str {get;set;} }
et une méthodeint f(I1 value) { return value.str.Length; }
. Si cela existe dans une bibliothèque, le compilateur ne peut pas savoir à quoi cette bibliothèque sera liée, donc si leset
test aura été appelé avant leget
, Le champ de sauvegarde peut ne pas être explicitement initialisé, mais il doit compiler ce code.f
. Il serait généré lors de la compilation des constructeurs. Si vous laissez un constructeur avec un champ éventuellement non initialisé, ce serait une erreur. Il peut également y avoir des restrictions sur l'appel des méthodes de classe et des getters avant que tous les champs ne soient initialisés.Bonnes réponses ci-dessus, mais j'ai pensé publier une réponse beaucoup plus simple / plus courte pour que les gens paresseux en lisent une longue (comme moi).
Classe
La propriété
Boo
peut ou non avoir été initialisée dans le constructeur. Donc, quand il le trouve,return Boo;
il ne suppose pas qu'il a été initialisé. Il supprime simplement l'erreur.Fonction
Les
{ }
caractères définissent la portée d'un bloc de code. Le compilateur parcourt les branches de ces{ }
blocs en gardant une trace des choses. Il peut facilement dire qu'ilBoo
n'a pas été initialisé. L'erreur est alors déclenchée.Pourquoi l'erreur existe-t-elle?
L'erreur a été introduite pour réduire le nombre de lignes de code nécessaires pour sécuriser le code source. Sans l'erreur, ce qui précède ressemblerait à ceci.
À partir du manuel:
Référence: https://msdn.microsoft.com/en-us/library/4y7h161d.aspx
la source