何时从属性getter或setter中抛出异常合适?何时不合适?为什么?在此主题上的外部文档链接将会很有帮助...谷歌搜索结果惊人地少。
何时从属性getter或setter中抛出异常合适?何时不合适?为什么?在此主题上的外部文档链接将会很有帮助...谷歌搜索结果惊人地少。
微软有关于如何设计属性的建议,可以在http://msdn.microsoft.com/en-us/library/ms229006.aspx找到。
实际上,他们建议属性getter应该是轻量级的访问器,并且始终安全可用。如果抛出异常是必要的话,则建议重新设计getter为方法。对于setter来说,他们表示异常是一种适当和可接受的错误处理策略。
对于索引器,微软表示getters和setters都可以抛出异常,事实上,.NET库中的许多索引器都是这样做的。最常见的异常是ArgumentOutOfRangeException
。
以下是不希望在属性getter中抛出异常的一些很好的理由:
obj.PropA.AnotherProp.YetAnother
- 对于这种语法,决定在何处注入异常捕获语句变得困难。顺带一提,应该知道,仅因为属性“未设计”抛出异常,并不意味着它不会抛出异常;它很容易调用会导致异常的代码。即使是分配新对象(如字符串)的简单操作也可能导致异常。您应该始终以防御性编写代码,并从任何您调用的地方预期异常。
Nullable<T>.Value
的getter会抛出异常。可以使用GetValue()
方法来完成,但是对我来说,使用属性似乎更好。 - Aidiakapi从setter中抛出异常没有问题。毕竟,有什么更好的方法来指示给定属性的值无效呢?
对于getter来说,通常不鼓励这样做,并且可以很容易地解释:通常情况下,属性getter报告对象的当前状态。因此,在getter抛出异常的情况下,唯一合理的情况是状态无效。但是,通常认为设计您的类的时候应该使其不可能通过正常方式获得无效对象,或者将其置于无效状态(即始终在构造函数中确保完全初始化,并尝试使方法与状态有效性和类不变式相关联)。只要遵守这个规则,您的属性getter就不会进入必须报告无效状态的情况,因此不会抛出异常。
我知道一个例外,这实际上是一个相当重要的例外:任何实现IDisposable
的对象。 Dispose
专门用作将对象置于无效状态的方法,甚至有一个特殊的异常类ObjectDisposedException
用于在这种情况下使用。在对象被处理后,从任何类成员,包括属性getter(但不包括Dispose
本身)抛出ObjectDisposedException
是很正常的。
IDisposable
成员在 Dispose
后都变得无用的概念。如果调用成员需要使用 Dispose
已经不可用的资源(例如,成员将从已关闭的流中读取数据),则该成员应抛出 ObjectDisposedException
而不是泄漏例如 ArgumentException
,但如果有一个表单,其中的属性表示某些字段中的值,那么允许在处理后读取这些属性(产生最后键入的值)似乎比要求... - supercatDispose
推迟到之后。在某些情况下,其中一个线程可能会在另一个线程关闭它时对对象进行阻塞读取,并且数据可能在Dispose
之前的任何时间到达,此时有助于让Dispose
截断传入的数据,但允许先前接收到的数据被读取。在不需要存在的情况下,不应在Close
和Dispose
之间强制进行人为区分。 - supercatGet...
方法而不是属性获取器。一个例外是当你必须实现需要你提供属性的现有接口时。 - Pavel Minaev对于 getter,几乎从不适用,而对于 setter,有时适用。
这类问题的最佳资源是 Cwalina 和 Abrams 的《框架设计指南》,它可以作为一本纸质书籍购买,也有大量在线可用的内容。
来自第 5.2 节:属性设计
避免从属性 getter 中抛出异常。属性 getter 应该是简单操作,并且不应该有前提条件。如果 getter 可能会抛出异常,那么它可能需要重新设计为一个方法。请注意,此规则不适用于索引器,在其中我们预期由于验证参数而引发异常。
请注意,此指南仅适用于属性 getter。在属性 setter 中抛出异常是可以的。
Dispose()
后,如果有请求属性值的操作,应该考虑抛出ObjectDisposedException
。因此,指导方针似乎应该是“尽量避免从属性 getter 中抛出异常,除非该对象已被处理,此时应该考虑抛出 ObjectDisposedException”。 - Scott Dorman我有一段代码,不确定应该抛出哪个异常。
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);
}
我通过在构造函数中强制将属性作为参数来防止模型在一开始就为空。
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);
}
这些都在MSDN中有详细记录(如其他答案中所链接的),但这里有一个经验法则...
在setter中,如果您的属性应该进行类型验证之上的验证。例如,名为PhoneNumber的属性可能应该具有正则表达式验证,并且如果格式无效,则应抛出错误。
对于getter,在值为空时可能需要处理,但最有可能是您希望在调用代码中处理它(根据设计指南)。
关于异常的一个好方法是将其用于为自己和其他开发人员记录代码,具体如下:
异常应该用于表示异常程序状态。这意味着你可以在任何地方编写它们!
你可能想将它们放在getter中的一个原因是为了记录类的API - 如果软件在程序员试图错误使用它时立即抛出异常,那么他们就不会错误使用它!例如,在数据读取过程中进行验证时,如果数据中存在致命错误,则继续访问处理结果可能没有意义。在这种情况下,您可能希望使获取输出在出现错误时抛出异常,以确保另一个程序员检查此条件。
它们是记录子系统/方法/任何内容的假设和边界的一种方式。通常情况下,它们不应该被捕获!这也是因为如果系统按预期方式协同工作,它们永远不会被抛出:如果发生异常,它表明某段代码的假设未得到满足 - 例如,它与周围的世界交互的方式与最初预期的方式不同。如果你捕获了为此目的编写的异常,那么很可能意味着系统已经进入了不可预测/不一致的状态 - 这最终可能导致崩溃或数据损坏或类似的问题,这很可能更难以检测/调试。
异常消息是报告错误的一种非常粗略的方式 - 它们无法被集中收集,而且只包含一个字符串。这使它们不适合报告输入数据的问题。在正常运行中,系统本身不应该进入错误状态。因此,它们中的消息应该为程序员设计,而不是为用户设计 - 输入数据中出现的错误可以以更合适(定制)的格式发现并传达给用户。
这个规则的例外是像IO这样的东西,其中异常不受您的控制,也无法提前检查。