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

时间:2009-09-28 18:01:48

标签: c# .net exception properties

何时从属性getter或setter中抛出异常是否合适?什么时候不合适?为什么?关于这个主题的外部文件的链接会有所帮助......谷歌出人意料地很少。

8 个答案:

答案 0 :(得分:121)

Microsoft提供了有关如何在http://msdn.microsoft.com/en-us/library/ms229006.aspx

设计属性的建议

基本上,他们建议属性getter是轻量级访问器,总是可以安全地调用。如果需要抛出异常,他们建议重新设计getter作为方法。对于setter,他们指出异常是一种适当且可接受的错误处理策略。

对于索引器,Microsoft表示getter和setter都可以抛出异常。事实上,.NET库中的许多索引器都是这样做的。最常见的例外是ArgumentOutOfRangeException

有一些很好的理由可以解释为什么你不想在属性getter中抛出异常:

  • 因为属性“看起来”是字段,所以它们总是可以抛出(按设计)异常;而对于方法,程序员接受培训以期望并调查异常是否是调用该方法的预期结果。
  • Getters被很多.NET基础结构使用,比如序列化程序和数据绑定(例如WinForms和WPF) - 在这种情况下处理异常会很快成为问题。
  • 当您查看或检查对象时,调试器会自动评估属性getter。这里的一个例外可能是混乱并减慢您的调试工作。出于同样的原因,在属性中执行其他昂贵的操作(例如访问数据库)也是不可取的。
  • 属性通常用在链接约定中:obj.PropA.AnotherProp.YetAnother - 使用这种语法决定在哪里注入异常捕获语句会成为问题。

作为旁注,人们应该知道,仅仅因为某个属性未设计来抛出异常,这并不意味着它不会;它可以很容易地调用代码。即使是分配新对象(如字符串)的简单操作也可能导致异常。您应始终以防御性方式编写代码,并期望从您调用的任何内容中获得异常。

答案 1 :(得分:34)

从setter中抛出异常没有错。毕竟,有什么更好的方法来表明该值对于给定的属性无效?

对于吸气剂,它通常是不受欢迎的,并且可以很容易地解释:一般来说,属性吸气剂报告物体的当前状态;因此,唯一一个吸气器投掷合理的情况是状态无效。但是通常认为设计类是一个好主意,以至于最初无法获得无效对象,或者通过常规方式将其置于无效状态(即始终确保构造函数中的完全初始化,以及尝试使方法在状态有效性和类不变量方面是异常安全的。只要你坚持这个规则,你的财产获取者就不应该陷入他们必须报告无效状态的情况,因此永远不会抛出。

我知道有一个例外,它实际上是一个相当重要的例外:任何实现IDisposable的对象。 Dispose专门用于将对象置于无效状态,并且在这种情况下甚至可以使用特殊的异常类ObjectDisposedException。在处理完对象后,从任何类成员抛出ObjectDisposedException是完全正常的,包括属性getter(并排除Dispose本身)。

答案 2 :(得分:24)

它几乎不适用于吸气剂,有时适用于定型器。

这些问题的最佳资源是Cwalina和Abrams的“框架设计指南”;它可作为装订书使用,其中大部分也可在线获取。

从第5.2节:物业设计

  

避免抛出异常   财产瘾君子。物业吸气剂   应该是简单的操作和应该   没有先决条件。如果是一个吸气剂   它应该抛出异常   可能会被重新设计成一种方法。   请注意,此规则不适用于   索引器,我们期待的地方   验证的例外情况   争论。

     

请注意,本指南仅适用   财产吸气剂。抛出是可以的   属性设置器中的异常。

答案 3 :(得分:2)

Exceptions的一个不错的方法是使用它们为自己和其他开发人员记录代码,如下所示:

异常应该是特殊的程序状态。这意味着可以随时随地写下它们!

您可能希望将它们放入getter中的一个原因是记录类的API - 如果软件在程序员尝试使用它时错误地抛出异常,那么它们就不会错误地使用它!例如,如果您在数据读取过程中进行了验证,那么如果数据中存在致命错误,则可能无法继续并访问该过程的结果。在这种情况下,如果出现错误,您可能希望获取输出抛出,以确保其他程序员检查此情况。

它们是记录子系统/方法/任何事物的假设和边界的一种方式。在一般情况下,他们不应被抓住!这也是因为如果系统以预期的方式一起工作,它们永远不会被抛出:如果发生异常则表明不满足一段代码的假设 - 例如它不会与周围的世界交互它原本打算用于。如果您捕获为此目的而编写的异常,则可能意味着系统已进入不可预测/不一致的状态 - 这可能最终导致数据或类似的崩溃或损坏,这可能更难以检测/调试。 / p>

异常消息是报告错误的一种非常粗略的方式 - 它们无法集中收集并且只包含字符串。这使它们不适合报告输入数据中的问题。在正常运行中,系统本身不应进入错误状态。因此,它们中的消息应该为程序员而不是用户设计 - 输入数据中出错的东西可以被发现并以更合适的(自定义)格式传递给用户。

此规则的例外情况(哈哈!)就像IO一样,例外情况不在您的控制之下,无法提前检查。

答案 4 :(得分:1)

这些都记录在MSDN中(与其他答案相关联)但这是一般的经验法则......

在setter中,如果您的属性应在上面和下面进行验证。例如,一个名为PhoneNumber的属性应该具有正则表达式验证,如果格式无效,则应该抛出错误。

对于getter,可能在值为null时,但很可能是您希望在调用代码上处理的内容(根据设计指南)。

答案 5 :(得分:0)

MSDN:捕获和投掷标准异常类型

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

答案 6 :(得分:0)

这是一个非常复杂的问题和答案取决于您的对象的使用方式。根据经验,属于“后期绑定”的属性获取者和设置者不应该抛出异常,而具有“早期绑定”的属性应该在需要时抛出异常。顺便说一句,微软的代码分析工具在我看来过于狭隘地定义了属性的使用。

“后期绑定”意味着通过反射找到属性。例如,Serializeable“属性用于通过其属性对对象进行序列化/反序列化。在这种情况下抛出异常会以灾难性方式破坏事物,并且不是使用异常来生成更强大代码的好方法。” p>

“早期绑定”意味着编译器在代码中绑定了属性使用。例如,当您编写的某些代码引用属性getter时。在这种情况下,可以在有意义时抛出异常。

具有内部属性的对象具有由这些属性的值确定的状态。表示对对象内部状态有意识且敏感的属性的属性不应用于后期绑定。例如,假设您有一个必须打开,访问,然后关闭的对象。在这种情况下,在不先调用open的情况下访问属性应该会导致异常。假设,在这种情况下,我们不抛出异常,我们允许代码访问值而不抛出异常?代码看起来很开心,即使它从一个无意义的getter获得了一个值。现在我们已经将调用getter的代码置于糟糕的情况,因为它必须知道如何检查值以查看它是否无意义。这意味着代码必须假设它从属性getter获得的值才能验证它。这就是编写错误代码的方式。

答案 7 :(得分:0)

我有这个代码,我不确定要抛出哪个异常。

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

我通过强制它在构造函数中作为参数来防止模型首先使属性为null。

{{1}}