从属性设置器中抛出什么异常?

56
我有一个字符串属性,因为数据与数据库相关联,所以有最大长度要求。如果调用者试图设置超过此长度的字符串,我应该抛出什么异常?
例如,这段 C# 代码:
public string MyProperty
{
    get
    {
        return _MyBackingField;
    }
    set
    {
        if (value.Length > 100)
            throw new FooException("MyProperty has a maximum length of 100.");

        _MyBackingField = value;
    }
}

我考虑了 ArgumentException,但感觉不太合适。从技术上讲,它是一个函数 - MyProperty_set(string value) - 因此可以为 ArgumentException 辩护,但在消费者看来,它并没有作为函数被调用 - 它在赋值运算符的右侧。

这个问题可能也可以扩展到包括在属性设置器中进行的所有数据验证,但我特别关注上述情况。

6个回答

51

使用反编译工具 Reflector 查看 mscorlib.dll,在与 System.String.StringBuilder.Capacity 类似的情况下,Microsoft 使用了类似如下的 ArgumentOutOfRangeException():

public int PropertyA
{
    get
    {
        return //etc...
    }
    set
    {
        if (condition == true)
        {
            throw new ArgumentOutOfRangeException("value", "/* etc... */");
        }
        // ... etc
    }
}

18
参数越界异常 - 当调用的方法定义的允许范围之外的参数值时抛出的异常。... 在我看来,Mono 应该修复他们的代码。 - Peter Lillevold
1
已确认:在此处 https://referencesource.microsoft.com/#mscorlib/system/text/stringbuilder.cs,7529ccd440df5320 - Paolo Fulgoni

19

对我来说,ArgumentException(或其子类)更有意义,因为您提供的参数(值)无效,这正是ArgumentException被创建的原因。


8
我不会完全抛出异常。相反,我将允许任意长度的字符串,然后在保存之前调用类上的单独的“验证”方法。特别是如果您使用数据绑定,从属性设置器抛出异常可能会使您陷入困境。
当数据绑定时,建议实现IDataErrorInfo接口。它具有一个Error属性,该属性提供了关于对象属性错误的总体描述,以及一个Item[columnName]属性,该属性具体说明了每个属性的错误信息。
从属性设置器中抛出异常的问题在于程序员会忘记捕获它们。这在一定程度上取决于您期望获取的数据有多干净。在这种情况下,我预计长字符串长度是常见的而不是异常的,因此使用异常将是“异常流控制”。
引用微软的设计指南以开发类库

如果可能的话,请勿在正常控制流中使用异常。除了系统故障和具有潜在竞争条件的操作之外,框架设计人员应该设计API,以便用户可以编写不会引发异常的代码。例如,您可以提供一种在调用成员之前检查前提条件的方法,以便用户可以编写不会引发异常的代码。


7
尽管我个人完全不同意(验证毕竟是设置器的工作 - 否则,除了简单的字段=值;返回字段; getter / setter之外就没有其他意义了 - 我喜欢你提出数据绑定可以让你陷入困境这一点。 - Michael Stum
2
在属性设置器中抛出异常是可以的,但我不会在属性获取器中抛出异常。 - Patrick Peters
@陷阱:我想这取决于你设计类的目的。在你希望数据绑定到该类的对象的情况下,当用户输入过多字符时,你不希望它爆炸。实现System.ComponentModel.IDataErrorInfo是解决此问题的标准方式。在setter中,你可以设置一个名为IsMyPropertyToLong的内部标志,而不是抛出异常。稍后,你可以使用它来从this[string]返回正确的错误消息。 - Martin Brown
10
ArgumentException和ArgumentOutOfRangeException是“愚蠢异常”,它们正当地用于检查约束条件。如果用户遇到这些可预防的异常,那么这是他们代码中的一个错误。 - mbx
很高兴看到提到了这个替代方案,但在这种情况下抛出异常也是可以的。毕竟,设置文本框的最大长度非常简单。Validate方法是验证规则依赖于其他属性的好方法。您可能不知道哪些属性先设置,因此最好在最后检查。 - HappyNomad
显示剩余2条评论

6
记得计算机科学中有许多问题都是通过添加额外的间接层来解决的吗?
一种方法是创建一个新类型,例如FixedLengthString。它将是那种验证初始化字符串长度的实例 - 并且具有转换运算符以从普通字符串进行类型转换。如果您的属性设置器以这种类型作为其参数,则任何违规行为都将变成类型转换异常,而不是参数/属性异常。
在实践中,我很少这样做。这有点像过分使用面向对象 - 但在某些情况下,它可能是一种有用的技术,因此我在此提及以供完整性考虑。

3
如何处理将最多100个字符的FixedLengthString传递给要求最多50个字符的FixedLengthString属性? - Martin Brown
@Martin Brown 简单,创建一个新类型 FixedLengthAt50String 继承自 FixedLengthString ;) - Kirill

5
public IPAddress Address
{
    get
    {
        return address;
    }
    set
    {
        if(value == null)
        {
            throw new ArgumentNullException("value");
        }
        address = value;
    }
}

通过MSDN获得的信息。


不错的观点,尽管这仅适用于空值情况。然而,它确实支持ArgumentException系列的使用,因此参考值+1。 - lc.

-3

尽可能使用现有的异常。在这种情况下,使用InvalidOperationException,因为传入的值会使对象处于不一致状态。当需要特定处理自定义异常时,可以创建自定义异常。在这种情况下,您只需抛出带有一些文本的异常,因此请使用InvalidOperationException。

在抛出InvalidOperationException时,请显示已传递给此setter的值。


当然,你可以拥有最好的两个世界,并创建自己的异常类型,它派生自InvalidOperationException。当然,你仍然需要付出一些额外的代码和代码复杂性。这只是一个小代价,但在权衡之后,你可能仍然认为它不值得。 - philsquared
4
这种情况不应该引发“InvalidOperationException”异常。有问题的是值,而不是设置该值的操作。 - Trap

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接