最佳做法:从属性抛出exception

什么时候从属性获取器或设置器中抛出exception是合适的? 什么时候不合适? 为什么? 关于这个问题的外部文件的链接将是有帮助的…谷歌出乎意料地less了一点。

Microsoft在http://msdn.microsoft.com/zh-cn/library/ms229006.aspx上提供有关如何devise属性的build议;

从本质上讲,他们build议属性获取者是总是可以安全调用的轻量级访问器。 他们build议重新devisegetter方法,如果你需要抛出exception。 对于setter来说,它们表明exception是一个合适的和可接受的error handling策略。

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

为什么不想在属性获取器中抛出exception呢?有一些非常好的理由:

  • 因为属性“出现”为字段,所以他们可以抛出(按devise)exception并不总是显而易见的。 而通过方法,程序员被训练去期望和调查exception是否是调用方法的预期结果。
  • Getters被许多.NET基础设施所使用,比如序列化器和数据绑定(例如在WinForms和WPF中) – 在这种情况下处理exception可能会迅速变成问题。
  • 当您观察或检查对象时,属性获取器将由debugging器自动进行评估。 这里的例外可能会造成混淆并放慢您的debugging工作。 由于相同的原因,在属性中执行其他昂贵的操作(如访问数据库)也是不可取的。
  • 属性通常用于链接约定中: obj.PropA.AnotherProp.YetAnother – 使用这种语法,决定注入exceptioncatch语句的位置变得有问题。

作为一个侧面说明,应该意识到,仅仅因为一个财产不是为了抛出一个例外,这并不意味着它不会; 它可以很容易地调用代码。 即使分配一个新对象(比如一个string)的简单行为也可能导致exception。 你应该总是防守地编写你的代码,并且期望你调用的任何东西都有exception。

从setter抛出exception没有任何问题。 毕竟,有什么更好的方法来表明这个值对某个给定的属性是无效的?

对于吸气者来说,它通常是被折磨的,而且可以很容易地解释:属性吸气者通常报告对象的当前状态; 因此,吸气剂投掷是合理的唯一情况是该状态无效。 但是,一般认为devise你的类是一个好主意,这样一开始就不可能获得一个无效的对象,或者通过正常的方式把它变成无效的状态(也就是总是确保在构造函数中完全初始化,尝试使方法在状态有效性和类不variables方面是exception安全的)。 只要你坚持这个规则,你的财产获得者就不应该陷入必须报告无效状态的情况,从而不会抛出。

我知道有一个例外,它实际上是一个比较主要的IDisposable :任何实现IDisposable对象。 Dispose专门用于使对象进入无效状态,甚至还有一个特殊的exception类ObjectDisposedException ,在这种情况下使用。 在抛弃对象之后,从任何类成员抛出ObjectDisposedException是完全正常的,包括属性获取器(不包括Dispose本身)。

对于吸气者来说几乎是不合适的,有时适合于吸气者。

这些问题的最佳资源是Cwalina和Abrams的“框架devise指南” 它可以作为一个绑定的书,大部分也可以在网上。

从第5.2节:物业devise

避免从属性获取者抛出exception。 财产获得者应该是简单的操作,不应该有先决条件。 如果一个getter可以抛出一个exception,应该重新devise一个方法。 请注意,这个规则不适用于索引器,我们确实期望exception作为validation参数的结果。

请注意,本指南仅适用于属性获取者。 在属性设置器中抛出exception是可以的。

这是所有MSDNlogging(链接到其他答案),但这里是一个一般的经验法则…

在二传手中,如果你的财产应该被validation超越types。 例如,名为PhoneNumber的属性可能应该有正则expression式validation,如果格式无效,则应该抛出一个错误。

对于getter来说,可能当这个值为null时,但最有可能的是你想在调用代码上处理的东西(按照devise指南)。

一个不错的方法是使用它们为自己和其他开发者编写代码,如下所示:

例外情况应该是特殊的程序状态。 这意味着把它们写在任何地方都可以!

你可能想要把它们放在getter中的一个原因是logging一个类的API – 如果软件在程序员尝试错误地使用它的时候抛出一个exception,那么他们就不会使用它了! 例如,如果您在数据读取过程中进行validation,那么如果数据中存在致命错误,则可以继续并访问过程的结果可能是没有意义的。 在这种情况下,如果出现错误,您可能希望得到输出,以确保另一个程序员检查这种情况。

它们是logging子系统/方法/任何事物的假设和边界的一种方式。 在一般情况下,他们不应该被抓到! 这也是因为如果系统以预期的方式一起工作,它们从不会被抛出:如果发生exception,则表明不能满足一段代码的假设 – 例如,它不与周围的世界进行交互它最初的目的是。 如果您发现为此目的而写的exception,可能意味着系统进入了不可预知/不一致的状态 – 这可能最终导致数据或类似的崩溃或损坏,这可能更难以检测/debugging。

exception消息是报告错误的一种非常粗糙的方式,它们不能被集体收集,只能包含一个string。 这使得他们不适合报告input数据中的问题。 在正常运行时,系统本身不应该进入错误状态。 由于这个原因,其中的信息应该是为程序员devise的,而不是为用户devise的 – input数据中的错误信息可以被发现并以更合适的(自定义)格式传递给用户。

这个规则的例外(哈哈!)就是IO这样的事情,例外情况不在你的控制之下,不能事先检查。

MSDN:捕捉和抛出标准exceptiontypes

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

这是一个非常复杂的问题和答案取决于你的对象是如何使用的。 作为一个经验法则,“迟绑定”的属性获得者和设置者不应该抛出exception,而只有“早绑定”的属性才会在需要时抛出exception。 顺便说一下,在我看来,微软的代码分析工具正在定义对属性的使用太狭隘。

“后期约束”是指通过反思find属性。 例如,“Serializeable”属性用于通过对象的属性对对象进行序列化/反序列化,在这种情况下抛出一个exception以一种灾难性的方式破坏事物,而不是使用exception来生成更健壮的代码的好方法。

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

具有内部属性的对象具有由这些属性的值确定的状态。 表示属性的属性对对象的内部状态有意识和敏感的属性不应该用于后期绑定。 例如,假设您有一个必须打开,访问,然后closures的对象。 在这种情况下访问属性而不先调用open会导致exception。 假设在这种情况下,我们不会抛出exception,并且允许代码访问一个值而不抛出exception? 代码看起来很开心,即使它从一个无意义的getter获得了一个值。 现在我们把调用getter的代码放在一个不好的情况下,因为它必须知道如何检查这个值是否是无意义的。 这意味着代码必须对从属性获取器获得的值进行假设以validation它。 这是多么糟糕的代码被写入。

我有这个代码,我不确定抛出哪个exception。

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