【问题标题】:Constructor restrictions构造函数限制
【发布时间】:2013-03-04 06:04:08
【问题描述】:

我想知道是否有办法限制构造中的值。这是我的代码:

class Student : Human 
{
    private double Grade;

    public Student(string FirstName, string LastName, double Grade)
        : base(FirstName, LastName)
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
        this.Grade = Grade;
    }
}

当我创建一个新学生时,我想将 Grade 限制在 >= 2.00 和

【问题讨论】:

  • 不要认为你可以让它成为编译时错误,但是,假设 Grade 是用户输入,你可以运行检查以确保它在你的范围内,然后让用户知道是否失败。
  • 你真的想要 double 吗?还是你想要十进制?请记住,double 不能精确表示分母上不是 2 次方的分数。

标签: c# class restriction


【解决方案1】:

你可以检查它并在运行时抛出一个异常,像这样:

if (grade < 2.00 || grade > 6.00)
    throw new ArgumentOutOfRangeException("grade");

始终将此类条件放在方法或构造函数的开头。我什至把它们放在自己的#region 中(但这是我个人的偏好):

public Student(string firstName, string lastName, double grade)
    : base(firstName, lastName)
{
    #region Contract
    if (grade < 2.00 || grade > 6.00)
        throw new ArgumentOutOfRangeException("grade");
    #endregion
    this.FirstName = firstName;
    this.LastName = lastName;
    this.Grade = grade;
}

但是,有一种方法可以使用代码合同获得此类事情的编译时警告。您可以下载Code Contracts on MSDN 并在那里您还可以找到文档。它仅与非 Express 版本的 Visual Studio 集成,由 Microsoft 编写。它将检查方法调用是否可能遵守您指定的合同。您的代码将变为:

using System.Diagnotistics.Contracts;

public Student(string firstName, string lastName, double grade)
    : base(firstName, lastName)
{
    #region Contract
    Contract.Requires<ArgumentOutOfRangeException>(grade >= 2.00);
    Contract.Requires<ArgumentOutOfRangeException>(grade <= 6.00);
    #endregion
    this.FirstName = firstName;
    this.LastName = lastName;
    this.Grade = grade;
}

【讨论】:

  • +1 表示 ArgumentOutOfRangeException,但不同意该区域。
  • 为什么不合并成一个 Requires 子句? Contract.Requires&lt;ArgumentOutOfRangeException&gt;(grade &gt;= 2.00 &amp;&amp; grade &lt;= 6.00);
  • @Andy 代码合同过去不擅长处理复合要求/确保和不变量。我不确定是否仍然如此,但为了便于阅读和可读性(长线等),我将它们分开。
【解决方案2】:
class Student : Human 
{
    private double Grade;

    public Student(string FirstName, string LastName, double Grade)
        : base(FirstName, LastName)
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
        this.Grade = Grade;
        if(Grade >= 2 and Grade <= 6){
            throw new Exception("Incorrect grade input");
    }
}

或者抛出你想要的相关异常,例如ArgumentOutOfException 等...

【讨论】:

  • 异常由程序员决定,由于我们没有足够的信息说明为什么等级不应该在 1 到 7 之间,因此不应该意味着应该抛出特定的异常,例如ArgumentOutOfException。
  • @SpaceApple 有一条一般规则,就是永远不要抛出异常。它违反了 .Net 最佳实践。
  • 是的。 ArgumentException 在这种情况下也无效,因为它可能无法满足程序员的需求。但我确实理解你的意思。
【解决方案3】:
class Student : Human 
{
    private double Grade;

    public Student(string FirstName, string LastName, double Grade)
        : base(FirstName, LastName)
    {
        if (Grade < 2 || Grade > 6)
            throw new ArgumentOutOfRangeException("Grade must be between 2 and 6");

        this.FirstName = FirstName;
        this.LastName = LastName;
        this.Grade = Grade;
    }
}

【讨论】:

    【解决方案4】:
    public Student(string FirstName, string LastName, double Grade)
        : base(FirstName, LastName)
        {
         if(Grade >= 2.0 || Grade <= 6.00)
          throw  new ArgumentException("your message");     
        }
    

    【讨论】:

      【解决方案5】:

      你可以抛出异常:

      class Student : Human 
      {
          private double Grade;
      
          public Student(string FirstName, string LastName, double Grade)
              : base(FirstName, LastName)
          {
              if (Grade >= 2 && Grade <= 6) { 
                throw new ArgumentOutOfRangeException();
              }
      
              this.FirstName = FirstName;
              this.LastName = LastName;
              this.Grade = Grade;
          }
      }
      

      如果您使用Microsoft Code Contracts,您也可以这样做:

      class Student : Human  {
          private double Grade;
      
          public Student(string FirstName, string LastName, double Grade)
              : base(FirstName, LastName)
          {
              System.Diagnotistics.Contracts.Contract.Requires<ArgumentOutOfRangeException>(Grade >= 2 && Grade <= 6);
      
              this.FirstName = FirstName;
              this.LastName = LastName;
              this.Grade = Grade;
          }
      }
      

      【讨论】:

      • 第一个示例中的条件是倒置的。它会在 2 到 6 年级(含)之间进行投掷,而不是在这些成绩之外投掷。
      • @LawrenceJohnston 谢谢,我已经修好了。
      【解决方案6】:

      如果您希望有任何编译时间检查的机会,您必须使用代码合同。它们本质上是运行时的异常,因此相当于带有异常的参数验证。

      public Student(string firstName, string lastName, decimal grade)
      {
          Contract.Requires(grade >= 2);
          Contract.Requires(grade <= 6);
      
          FirstName = firstName;
          LastName = lastName;
          Grade = grade;     
      }
      

      看到这个问题

      Design by contracts and constructors

      【讨论】:

        【解决方案7】:

        还有另一个选项,可能有点复杂,但会确保给定的范围,根本不使用double,而是创建一个名为Grade 的自定义类。您的 Grade 类将包含来自和到 double 的强制转换,并将强制验证。 这意味着所有 Grade 验证逻辑都在 Grade 结构中,而 Student 类只接收一个 Grade 对象而不必担心。

        public struct Grade
        {
            public static readonly double MinValue = 2.0;
            public static readonly double MaxValue = 6.0;
        
            private double value;
        
            public static explicit operator double(Grade grade)
            {
                return grade.value + MinValue;
            }
        
            public static explicit operator Grade(double gradeValue)
            {
                if (gradeValue < MinValue || gradeValue > MaxValue)
                    throw new ArgumentOutOfRangeException("gradeValue", "Grade must be between 2.0 and 6.0");
        
                return new Grade{ value = gradeValue - MinValue };
            }
        }
        

        你会这样使用它:

        double userInput = GetUserInputForGrade();
        Grade grade = (Grade)userInput; // perform explicit cast.
        Student student = new Student(firstName, lastName, grade);
        

        编辑:我已根据@EricLippert 的建议更新了代码,以使该类向开发人员公开其最小值/最大值。

        编辑 2: 再次更新 @JeppeStigNielsen 的建议。 value 字段现在存储 MinValue 的偏移量,因此对 default(Grade) 的调用将返回一个有效值(等于 MinValue),无论 0 是否在有效范围内。

        【讨论】:

        • 但是你失去了所有的操作符、所有的比较和相等方法,并且任何接受double的方法将不再接受你的Grade
        • @Virtlink:如果你错过了这些操作,请在类型 Grade 上实现它们。
        • 我喜欢这个想法,但我会让常量public static readonly fields。公开,因为我们希望用户能够知道双精度值何时超出范围。静态,因为它是类型范围的。并且是只读的,而不是恒定的,因为范围将来可能会发生变化。
        • @EricLippert 这个结构的问题是default(Grade)(或等效的new Grade())会为Grade 结构创建一个“非法”值0.0。在这种情况下,您的检查不会运行。一种解决方法是将 excess2.0 (MinValue) 相比存储在实例字段中。例如,如果实际成绩是2.5,您将存储0.5。然后,您的转换运算符必须在 return 之前添加(分别减去)MinValue
        • @JeppeStigNielsen:太棒了!请务必记住,结构的默认值始终是合法值。
        猜你喜欢
        • 1970-01-01
        • 2013-10-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-22
        • 2020-09-18
        • 2014-03-03
        • 1970-01-01
        相关资源
        最近更新 更多