【问题标题】:What exception to throw from a property setter?从属性设置器抛出什么异常?
【发布时间】:2010-10-12 15:32:02
【问题描述】:

我有一个具有最大长度要求的字符串属性,因为数据链接到数据库。如果调用者试图设置一个超过这个长度的字符串,我应该抛出什么异常?

例如,这个 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 提供一个案例,但在消费者眼中它并没有被称为函数 - 它位于作业的右侧操作员。

这个问题可能还可以扩展到包括在属性设置器中完成的各种数据验证,但我对上述案例特别感兴趣。

【问题讨论】:

    标签: c# validation exception properties


    【解决方案1】:

    用 Reflector 看看 mscorlib.dll,在类似 System.String.StringBuilder.Capacity 的情况下,微软使用 ArgumentOutOfRangeException() 类似于:

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

    【讨论】:

    【解决方案2】:

    对我来说 ArgumentException(或一个孩子)更有意义,因为您提供的参数(值)无效,这就是 ArgumentException 的创建目的。

    【讨论】:

      【解决方案3】:

      我根本不会抛出异常。相反,我会允许一个任意长度的字符串,然后在保存之前调用的类上有一个单独的“验证”方法。有许多场景,特别是如果您使用数据绑定,从属性设置器抛出异常可能会让您陷入混乱。

      从属性设置器抛出异常的麻烦在于程序员忘记捕获它们。这有点取决于您期望获得的数据有多干净。在这种情况下,我希望长字符串长度很常见而不是例外,因此使用异常将是“带有异常的流控制”。

      引用微软的Design Guidelines for Developing Class Libraries

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

      【讨论】:

      • 虽然我个人完全不同意(验证毕竟是 setter 的工作 - 否则除了简单的字段 = 值;返回字段;getter/setter 之外,没有任何意义。像这样你提出了 DataBinding 的观点,这确实会让你一团糟。
      • 在属性设置器中抛出异常是可以的,但我不会在属性获取器中抛出异常。
      • 延迟抛出异常不会让你抓住违规者。此外,如果程序员忘记捕获异常,那么它会跳到他的脸上,而不是无意中冒泡。
      • @Trap:我想这取决于您设计课程的目的。在您希望 DataBind 到类的对象的情况下,您不希望它在用户键入一对多字符时爆炸。实现 System.ComponentModel.IDataErrorInfo 是围绕这一点的标准方法。在设置器中,您设置了一个名为 IsMyPropertyToLong 之类的内部标志,而不是引发异常。稍后您使用它从 this[string] 返回正确的错误消息
      • ArgumentException 和 ArgumentOutOfRangeExceptions 是 "Boneheaded Exceptions",它们在这里被正确地用于检查约束。如果用户得到这些可预防的异常,那就是他们代码中的错误。
      【解决方案4】:

      还记得有多少计算机科学问题是通过增加额外的间接层级来解决的吗?

      例如,一种方法是创建一个新类型 FixedLengthString。这将是 that 类型的实例来验证它们初始化的字符串的长度 - 使用转换运算符从纯字符串进行类型转换。如果您的属性设置器将此类类型作为其参数,那么任何违规都将成为类型转换异常,而不是参数/属性异常。

      实际上我很少这样做。 OO 有点过头了——但在某些情况下它可能是一种有用的技术,所以为了完整起见,我在这里提到它。

      【讨论】:

      • 您将如何处理将设置为最多 100 个字符的 FixedLengthString 传递给需要设置为最多 50 个字符的 FixedLengthString 的属性?
      【解决方案5】:
      public IPAddress Address
      {
          get
          {
              return address;
          }
          set
          {
              if(value == null)
              {
                  throw new ArgumentNullException("value");
              }
              address = value;
          }
      }
      

      通过MSDN

      【讨论】:

      • 好点,虽然这是针对空情况的。但是,它确实支持使用ArgumentException 系列,所以+1 供参考。
      【解决方案6】:

      您可以使用 InvalidOperationException。那是一种妥协。我也不会费心使用 ArgumentException。

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

        • 当然,您可以两全其美,并创建自己的异常类型,该异常类型源自 InvalidOperationException。当然,您仍然需要为额外的代码和代码复杂性付出一些代价。这是一笔不小的成本,但在权衡之后你可能仍然认为它不值得
        • 在这种情况下,不应引发 InvalidOperationException。错的是值,而不是设置值的操作。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-21
        • 1970-01-01
        • 2020-12-30
        • 2011-11-04
        • 1970-01-01
        • 2013-12-28
        相关资源
        最近更新 更多