Quand est-il approprié de lever une exception depuis un getter ou un setter de propriété? Quand n'est-ce pas approprié? Pourquoi? Des liens vers des documents externes sur le sujet seraient utiles ... Google est étonnamment peu apparu.
Quand est-il approprié de lever une exception depuis un getter ou un setter de propriété? Quand n'est-ce pas approprié? Pourquoi? Des liens vers des documents externes sur le sujet seraient utiles ... Google est étonnamment peu apparu.
Réponses:
Microsoft a ses recommandations sur la façon de concevoir des propriétés à l' adresse http://msdn.microsoft.com/en-us/library/ms229006.aspx
Essentiellement, ils recommandent que les getters de propriété soient des accesseurs légers qui peuvent toujours être appelés en toute sécurité. Ils recommandent de redessiner les getters pour qu'ils deviennent des méthodes si des exceptions sont quelque chose que vous devez lancer. Pour les setters, ils indiquent que les exceptions sont une stratégie de gestion des erreurs appropriée et acceptable.
Pour les indexeurs, Microsoft indique qu'il est acceptable pour les getters et les setters de lever des exceptions. Et en fait, de nombreux indexeurs de la bibliothèque .NET le font. L'exception la plus courante étant ArgumentOutOfRangeException
.
Il y a de très bonnes raisons pour lesquelles vous ne voulez pas lever d'exceptions dans les getters de propriété:
obj.PropA.AnotherProp.YetAnother
- avec ce type de syntaxe, il devient problématique de décider où injecter les instructions exception catch.En remarque, il faut savoir que ce n'est pas parce qu'une propriété n'est pas conçue pour lever une exception que cela ne signifie pas qu'elle ne le sera pas; il pourrait facilement appeler le code qui le fait. Même le simple fait d'allouer un nouvel objet (comme une chaîne) peut entraîner des exceptions. Vous devez toujours écrire votre code de manière défensive et vous attendre à des exceptions de tout ce que vous invoquez.
Il n'y a rien de mal à lancer des exceptions de la part des setters. Après tout, quelle meilleure façon d'indiquer que la valeur n'est pas valide pour une propriété donnée?
Pour les getters, il est généralement mal vu, et cela peut être expliqué assez facilement: un getter de propriété, en général, rapporte l'état actuel d'un objet; ainsi, le seul cas où il est raisonnable pour un getter de lancer est celui où l'état est invalide. Mais il est également généralement considéré comme une bonne idée de concevoir vos classes de telle sorte qu'il ne soit tout simplement pas possible d'obtenir un objet invalide au départ, ou de le mettre dans un état invalide par des moyens normaux (c'est-à-dire toujours assurer une initialisation complète dans les constructeurs, et essayez de rendre les méthodes sans exception en ce qui concerne la validité d'état et les invariants de classe). Tant que vous vous en tenez à cette règle, vos getters de propriété ne devraient jamais se retrouver dans une situation où ils doivent signaler un état invalide, et donc ne jamais lancer.
Il y a une exception que je connais, et c'est en fait une exception assez importante: toute implémentation d'objet IDisposable
. Dispose
est spécifiquement conçu comme un moyen de mettre un objet dans un état invalide, et il existe même une classe d'exception spéciale ObjectDisposedException
, à utiliser dans ce cas. Il est parfaitement normal de lancer ObjectDisposedException
depuis n'importe quel membre de classe, y compris les récupérateurs de propriété (et s'excluant Dispose
lui-même), après que l'objet a été supprimé.
IDisposable
devraient être rendus inutiles après un Dispose
. Si l'invocation d'un membre nécessiterait l'utilisation d'une ressource qui Dispose
a rendu indisponible (par exemple, le membre lirait les données d'un flux qui a été fermé) le membre devrait lancer ObjectDisposedException
plutôt que de fuir par exemple ArgumentException
, mais si l'on a un formulaire avec des propriétés qui représentent le dans certains champs, il semblerait beaucoup plus utile de permettre la lecture de ces propriétés après l'élimination (donnant les dernières valeurs typées) que d'exiger ...
Dispose
seront différés jusqu'à ce que toutes ces propriétés aient été lues. Dans certains cas où un thread peut utiliser des lectures bloquantes sur un objet tandis qu'un autre le ferme, et où des données peuvent arriver à tout moment avant Dispose
, il peut être utile d'avoir Dispose
coupé les données entrantes, mais autoriser la lecture des données précédemment reçues. Il ne faut pas forcer une distinction artificielle entre Close
et Dispose
dans des situations où aucune n'aurait autrement besoin d'exister.
Get...
méthode à la place. Une exception ici est lorsque vous devez implémenter une interface existante qui vous oblige à fournir une propriété.
Il n'est presque jamais approprié sur un getter, et parfois approprié sur un setter.
La meilleure ressource pour ce genre de questions est "Framework Design Guidelines" par Cwalina et Abrams; il est disponible sous forme de livre relié, et une grande partie de celui-ci est également disponible en ligne.
De la section 5.2: Conception de la propriété
ÉVITEZ de lancer des exceptions à partir des récupérateurs de propriété. Les récupérateurs de propriétés doivent être des opérations simples et ne doivent pas avoir de conditions préalables. Si un getter peut lever une exception, il devrait probablement être repensé pour être une méthode. Notez que cette règle ne s'applique pas aux indexeurs, pour lesquels nous attendons des exceptions suite à la validation des arguments.
Notez que cette directive s'applique uniquement aux récupérateurs de propriétés. Il est possible de lever une exception dans un setter de propriétés.
ObjectDisposedException
une fois que l'objet a été Dispose()
appelé et que quelque chose demande par la suite une valeur de propriété? Il semble que les conseils devraient être "éviter de lancer des exceptions à partir des récupérateurs de propriété, à moins que l'objet n'ait été supprimé, auquel cas vous devriez envisager de lancer un ObjectDisposedExcpetion".
Une bonne approche des exceptions consiste à les utiliser pour documenter le code pour vous-même et pour d'autres développeurs comme suit:
Les exceptions devraient concerner les états de programme exceptionnels. Cela signifie que vous pouvez les écrire où vous voulez!
Une des raisons pour lesquelles vous voudrez peut-être les mettre dans des getters est de documenter l'API d'une classe - si le logiciel lève une exception dès qu'un programmeur essaie de l'utiliser de manière incorrecte, il ne l'utilisera pas mal! Par exemple, si vous avez une validation pendant un processus de lecture de données, il peut ne pas être logique de pouvoir continuer et d'accéder aux résultats du processus s'il y avait des erreurs fatales dans les données. Dans ce cas, vous souhaiterez peut-être obtenir la sortie de sortie s'il y avait des erreurs pour vous assurer qu'un autre programmeur vérifie cette condition.
Ils sont une manière de documenter les hypothèses et les limites d'un sous-système / méthode / peu importe. Dans le cas général, ils ne doivent pas être pris! C'est aussi parce qu'ils ne sont jamais lancés si le système fonctionne ensemble de la manière attendue: si une exception se produit, cela montre que les hypothèses d'un morceau de code ne sont pas satisfaites - par exemple, il n'interagit pas avec le monde qui l'entoure de la manière c'était initialement prévu. Si vous détectez une exception qui a été écrite à cet effet, cela signifie probablement que le système est entré dans un état imprévisible / incohérent - cela peut finalement conduire à un plantage ou à une corruption de données ou similaire, ce qui sera probablement beaucoup plus difficile à détecter / déboguer.
Les messages d'exception sont un moyen très grossier de signaler les erreurs - ils ne peuvent pas être collectés en masse et ne contiennent vraiment qu'une chaîne. Cela les rend impropres à signaler des problèmes dans les données d'entrée. En fonctionnement normal, le système lui-même ne doit pas entrer dans un état d'erreur. En conséquence, les messages qu'ils contiennent doivent être conçus pour les programmeurs et non pour les utilisateurs - les choses qui ne sont pas correctes dans les données d'entrée peuvent être découvertes et relayées aux utilisateurs dans des formats (personnalisés) plus appropriés.
L'exception (haha!) À cette règle est des choses comme IO où les exceptions ne sont pas sous votre contrôle et ne peuvent pas être vérifiées à l'avance.
Tout cela est documenté dans MSDN (comme lié à d'autres réponses), mais voici une règle générale ...
Dans le setter, si votre propriété doit être validée au-dessus et au-delà du type. Par exemple, une propriété appelée PhoneNumber devrait probablement avoir une validation regex et devrait générer une erreur si le format n'est pas valide.
Pour les getters, peut-être lorsque la valeur est nulle, mais c'est probablement quelque chose que vous voudrez gérer sur le code appelant (selon les directives de conception).
MSDN: intercepter et lancer des types d'exceptions standard
C'est une question très complexe et la réponse dépend de la façon dont votre objet est utilisé. En règle générale, les getters et les setters de propriété qui sont "à liaison tardive" ne devraient pas lancer d'exceptions, tandis que les propriétés avec exclusivement une "liaison anticipée" devraient lever des exceptions lorsque le besoin s'en fait sentir. BTW, l'outil d'analyse de code de Microsoft définit l'utilisation des propriétés de manière trop étroite à mon avis.
«liaison tardive» signifie que les propriétés sont trouvées par réflexion. Par exemple, l'attribut Serializeable "est utilisé pour sérialiser / désérialiser un objet via ses propriétés. Lancer une exception dans ce genre de situation casse les choses de manière catastrophique et n'est pas un bon moyen d'utiliser des exceptions pour créer un code plus robuste.
«Early binding» signifie qu'une propriété utilisée est liée dans le code par le compilateur. Par exemple, lorsque du code que vous écrivez fait référence à un getter de propriété. Dans ce cas, il est possible de lancer des exceptions lorsqu'elles ont un sens.
Un objet avec des attributs internes a un état déterminé par les valeurs de ces attributs. Les propriétés exprimant des attributs qui sont conscients et sensibles à l'état interne de l'objet ne doivent pas être utilisées pour la liaison tardive. Par exemple, disons que vous avez un objet qui doit être ouvert, accédé, puis fermé. Dans ce cas, accéder aux propriétés sans appeler open en premier devrait entraîner une exception. Supposons, dans ce cas, que nous ne lançions pas d'exception et que nous permettions au code d'accéder à une valeur sans lever d'exception? Le code semblera heureux même s'il a obtenu une valeur d'un getter qui n'a pas de sens. Maintenant, nous avons mis le code qui a appelé le getter dans une mauvaise situation car il doit savoir comment vérifier la valeur pour voir si elle n'a pas de sens. Cela signifie que le code doit faire des hypothèses sur la valeur qu'il a obtenue du getter de propriété afin de le valider. C'est ainsi que le mauvais code est écrit.
J'avais ce code où je ne savais pas quelle exception lancer.
public Person
{
public string Name { get; set; }
public boolean HasPets { get; set; }
}
public void Foo(Person person)
{
if (person.Name == null) {
throw new Exception("Name of person is null.");
// I was unsure of which exception to throw here.
}
Console.WriteLine("Name is: " + person.Name);
}
J'ai empêché le modèle d'avoir la propriété null en premier lieu en le forçant comme argument dans le constructeur.
public Person
{
public Person(string name)
{
if (name == null) {
throw new ArgumentNullException(nameof(name));
}
Name = name;
}
public string Name { get; private set; }
public boolean HasPets { get; set; }
}
public void Foo(Person person)
{
Console.WriteLine("Name is: " + person.Name);
}