简体   繁体   English

最佳实践:从属性中抛出异常

[英]Best practices: throwing exceptions from properties

When is it appropriate to throw an exception from within a property getter or setter? 何时从属性getter或setter中抛出异常是否合适? When is it not appropriate? 什么时候不合适? Why? 为什么? Links to external documents on the subject would be helpful... Google turned up surprisingly little. 关于这个主题的外部文件的链接将是有帮助的...谷歌出乎意料地少了。

Microsoft has its recommendations on how to design properties at http://msdn.microsoft.com/en-us/library/ms229006.aspx Microsoft在http://msdn.microsoft.com/en-us/library/ms229006.aspx上提供了有关如何设计属性的建议

Essentially, they recommend that property getters be lightweight accessors that are always safe to call. 从本质上讲,他们建议属性getter是轻量级访问器,总是可以安全地调用。 They recommend redesigning getters to be methods if exceptions are something you need to throw. 如果需要抛出异常,他们建议重新设计getter作为方法。 For setters they indicate that exceptions are an appropriate and acceptable error handling strategy. 对于setter,他们指出异常是一种适当且可接受的错误处理策略。

For indexers, Microsoft indicates that it is acceptable for both getters and setters to throw exceptions. 对于索引器,Microsoft表示getter和setter都可以抛出异常。 And in fact, many indexers in the .NET library do this. 事实上,.NET库中的许多索引器都是这样做的。 The most common exception being ArgumentOutOfRangeException . 最常见的例外是ArgumentOutOfRangeException

There are some pretty good reasons why you don't want to throw exceptions in property getters: 有一些很好的理由可以解释为什么你不想在属性getter中抛出异常:

  • Because properties "appear" to be fields, it is not always apparent that they can throw a (by-design) exception; 因为属性“看起来”是字段,所以它们总是可以抛出(按设计)异常; whereas with methods, programmers are trained to expect and investigate whether exceptions are an expected consequence of invoking the method. 而对于方法,程序员接受培训以期望和调查异常是否是调用该方法的预期结果。
  • Getters are used by a lot of .NET infrastructure, like serializers and databinding (in WinForms and WPF for example) - dealing with exceptions in such contexts can rapidly become problematic. 许多.NET基础结构都使用getter,比如序列化程序和数据绑定(例如WinForms和WPF) - 在这种情况下处理异常会很快成为问题。
  • Property getters are automatically evaluated by debuggers when you watch or inspect an object. 当您观察或检查对象时,调试器会自动评估属性getter。 An exception here can be confusing and slow down your debugging efforts. 这里的一个例外可能是混乱并减慢您的调试工作。 It's also undesirable to perform other expensive operations in properties (like accessing a database) for the same reasons. 出于同样的原因,在属性中执行其他昂贵的操作(例如访问数据库)也是不合需要的。
  • Properties are often used in a chaining convention: obj.PropA.AnotherProp.YetAnother - with this kind of syntax it becomes problematic to decide where to inject exception catch statements. 属性通常用在链接约定中: obj.PropA.AnotherProp.YetAnother - 使用这种语法,确定注入异常捕获语句的位置会成为问题。

As a side note, one should be aware that just because a property is not designed to throw an exception, that doesn't mean it won't; 作为旁注,人们应该意识到,仅仅因为某个属性不是为了抛出异常,这并不意味着它不会; it could easily be calling code that does. 它可以很容易地调用代码。 Even the simple act of allocating a new object (like a string) could result in exceptions. 即使是分配新对象(如字符串)的简单操作也可能导致异常。 You should always write your code defensively and expect exceptions from anything you invoke. 您应始终以防御性方式编写代码,并期望从您调用的任何内容中获得异常。

There's nothing wrong with throwing exceptions from setters. 从setter中抛出异常没有错。 After all, what better way to indicate that the value is not valid for a given property? 毕竟,有什么更好的方法来表明该值对于给定的属性无效?

For getters, it is generally frowned upon, and that can be explained pretty easily: a property getter, in general, reports the current state of an object; 对于getter来说,它通常是不受欢迎的,并且可以很容易地解释:一般来说,属性getter报告对象的当前状态; thus, the only case where it is reasonable for a getter to throw is when the state is invalid. 因此,唯一一个吸气器投掷合理的情况是状态无效。 But it is also generally considered to be a good idea to design your classes such that it is simply not possible to get an invalid object initially, or to put it into invalid state via normal means (ie, always ensure full initialization in constructors, and try make methods exception-safe with respect to state validity and class invariants). 但是通常认为设计类是一个好主意,以至于最初无法获得无效对象,或者通过常规方式将其置于无效状态(即始终确保构造函数中的完全初始化,以及尝试使方法在状态有效性和类不变量方面是异常安全的。 So long as you stick to that rule, your property getters should never get into a situation where they have to report invalid state, and thus never throw. 只要你坚持这个规则,你的财产获取者就不应该陷入他们必须报告无效状态的情况,因此永远不会抛出。

There is one exception I know of, and it's actually a rather major one: any object implementing IDisposable . 我知道有一个例外,它实际上是一个相当重要的例外:任何实现IDisposable对象。 Dispose is specifically intended as a way to bring object into an invalid state, and there's even a special exception class, ObjectDisposedException , to be used in that case. Dispose专门用于将对象置于无效状态,甚至还有一个特殊的异常类ObjectDisposedException ,在这种情况下使用。 It is perfectly normal to throw ObjectDisposedException from any class member, including property getters (and excluding Dispose itself), after the object has been disposed. Dispose对象之后,从任何类成员抛出ObjectDisposedException是完全正常的,包括属性getter(并且不包括Dispose本身)。

It is almost never appropriate on a getter, and sometimes appropriate on a setter. 它几乎不适用于吸气剂,有时适用于定型器。

The best resource for these sorts of questions is "Framework Design Guidelines" by Cwalina and Abrams; 这些问题的最佳资源是Cwalina和Abrams的“框架设计指南”; it's available as a bound book, and large portions of it are also available online. 它可作为装订书使用,其中大部分也可在线获取。

From section 5.2: Property Design 从第5.2节:财产设计

AVOID throwing exceptions from property getters. 避免从属性getter中抛出异常。 Property getters should be simple operations and should not have preconditions. 属性getter应该是简单的操作,不应该有前提条件。 If a getter can throw an exception, it should probably be redesigned to be a method. 如果getter可以抛出异常,则应该将其重新设计为方法。 Note that this rule does not apply to indexers, where we do expect exceptions as a result of validating the arguments. 请注意,此规则不适用于索引器,我们确实会在验证参数时遇到异常。

Note that this guideline only applies to property getters. 请注意,本指南仅适用于属性获取者。 It is OK to throw an exception in a property setter. 可以在属性设置器中抛出异常。

One nice approach to Exceptions is to use them to document code for yourself and other developers as follows: Exceptions的一个很好的方法是使用它们为自己和其他开发人员记录代码,如下所示:

Exceptions should be for exceptional program states. 例外情况应该是特殊的计划状态。 This means it's fine to write them wherever you want! 这意味着可以随心所欲地编写它们!

One reason you might want to put them in getters is to document the API of a class - if the software throws an exception as soon as a programmer tries to use it wrong then they wont use it wrong! 您可能希望将它们放入getter中的一个原因是记录类的API - 如果软件在程序员尝试使用它时错误地抛出异常,那么它们就不会错误地使用它! For instance if you have validation during a data reading process it may not make sense to be able to continue and access the results of the process if there were fatal errors in the data. 例如,如果您在数据读取过程中进行了验证,那么如果数据中存在致命错误,则可能无法继续并访问该过程的结果。 In this case you may want to make getting the output throw if there were errors to ensure that another programmer checks for this condition. 在这种情况下,如果出现错误,您可能希望获取输出抛出,以确保另一个程序员检查此情况。

They are a way of documenting the assumptions and boundaries of a subsystem/method/whatever. 它们是记录子系统/方法/等等的假设和边界的一种方式。 In the general case they should not be caught! 在一般情况下,他们不应被抓住! This is also because they are never thrown if the system is working together in the way expected: If an exception happens it shows that the assumptions of a piece of code are not met - eg it is not interacting with the world around it in the way it was originally intended to. 这也是因为如果系统以预期的方式一起工作,它们永远不会被抛出:如果发生异常则表明不满足一段代码的假设 - 例如它不会与周围的世界交互它原本打算用于。 If you catch an exception that was written for this purpose it probably means the system has entered an unpredictable/inconsistent state - this may ultimately lead to a crash or corruption of data or similar which is likely to be much harder to detect/debug. 如果您捕获为此目的而编写的异常,则可能意味着系统已进入不可预测/不一致的状态 - 这可能最终导致数据或类似的崩溃或损坏,这可能更难以检测/调试。

Exception messages are a very coarse way of reporting errors - they cannot be collected en-masse and only really contain a string. 异常消息是报告错误的一种非常粗略的方式 - 它们无法集中收集,只能真正包含字符串。 This makes them unsuitable for reporting problems in input data. 这使它们不适合报告输入数据中的问题。 In normal running the system itself should not enter an error state. 在正常运行中,系统本身不应进入错误状态。 As a result of this the messages in them should be designed for programmers and not for users - things that are wrong in input data can be discovered and relayed to users in more suitable (custom) formats. 因此,它们中的消息应该为程序员而不是用户设计 - 输入数据中出错的东西可以被发现并以更合适的(自定义)格式传递给用户。

The Exception (haha!) to this rule is things like IO where exceptions are not under your control and cannot be checked for in advance. 此规则的例外(哈哈!)就像IO这样的事情,例外情况不在您的控制之下,无法提前检查。

This is all documented in MSDN (as linked to in other answers) but here is a general rule of thumb... 这些都记录在MSDN中(与其他答案相关)但这是一般的经验法则......

In the setter, if your property should be validated above and beyond type. 在setter中,如果您的属性应该在类型之上和之外进行验证。 For example, a property called PhoneNumber should probably have regex validation and should throw an error if the format is not valid. 例如,一个名为PhoneNumber的属性应该具有正则表达式验证,如果格式无效,则应该抛出错误。

For getters, possibly when the value is null, but most likely that is something you will want to handle on the calling code (per the design guidelines). 对于getter,可能在值为null时,但很可能是您希望在调用代码上处理的内容(根据设计指南)。

MSDN: Catching and Throwing Standard Exception Types MSDN:捕获和抛出标准异常类型

http://msdn.microsoft.com/en-us/library/ms229007.aspx http://msdn.microsoft.com/en-us/library/ms229007.aspx

This is a very complex question and answer depends on how your object is used. 这是一个非常复杂的问题和答案取决于您的对象的使用方式。 As a rule of thumb, property getters and setters that are "late binding" should not throw exceptions, while properties with exclusively "early binding" should throw exceptions when the need arises. 根据经验,属于“后期绑定”的属性获取者和设置者不应该抛出异常,而具有“早期绑定”的属性应该在需要时抛出异常。 BTW, Microsoft's code analysis tool is defining the use of properties too narrowly in my opinion. 顺便说一句,微软的代码分析工具在我看来过于狭隘地定义了属性的使用。

"late binding" means that properties are found through reflection. “后期绑定”意味着通过反射找到属性。 For example the Serializeable" attribute is used to serialize/deserialize an object via its properties. Throwing an exception during in this kind of situation breaks things in a catastrophic way and is not a good way of using exceptions to make more robust code. 例如,Serializeable“属性用于通过其属性序列化/反序列化对象。在这种情况下抛出异常会以灾难性方式破坏事物,并且不是使用异常来制作更强大代码的好方法。

"early binding" means that a property use is bound in the code by the compiler. “早期绑定”意味着编译器在代码中绑定了属性使用。 For example when some code that you write references a property getter. 例如,当您编写的某些代码引用属性getter时。 In this case it is OK to throw exceptions when they make sense. 在这种情况下,可以在有意义时抛出异常。

An object with internal attributes has a state determined by the values of those attributes. 具有内部属性的对象具有由这些属性的值确定的状态。 Properties expressing attributes that are aware and sensitive to the object's internal state should not be used for late binding. 表示对对象内部状态有意识且敏感的属性的属性不应用于后期绑定。 For example, lets say you have an object that must be opened, accessed, then closed. 例如,假设您有一个必须打开,访问,然后关闭的对象。 In this case accessing the properties without calling open first should result in an exception. 在这种情况下,在不先调用open的情况下访问属性应该会导致异常。 Suppose, in this case, that we do not throw an exception and we allow the code access to a value without throwing an exception? 假设,在这种情况下,我们不抛出异常,我们允许代码访问值而不抛出异常? The code will seem happy even though it got a value from a getter that is non-sense. 代码看起来很开心,即使它从一个无意义的getter获得了一个值。 Now we have put the code that called the getter in a bad situation since it must know how to check the value to see if it is non-sense. 现在我们已经将调用getter的代码置于糟糕的情况,因为它必须知道如何检查值以查看它是否无意义。 This means that the code must make assumptions about the value that it got from the property getter in order to validate it. 这意味着代码必须假设它从属性getter获得的值才能验证它。 This is how bad code gets written. 这就是编写错误代码的方式。

I had this code where I was unsure of which exception to throw. 我有这个代码,我不确定抛出哪个异常。

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);
}

I prevented the model from having the property being null in the first place by forcing it as an argument in the constructor. 我通过强制它作为构造函数中的参数来防止模型首先使属性为null。

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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM