C#在不可变类中抛出异常

C#在不可变类中抛出异常,c#,class,exception,immutability,C#,Class,Exception,Immutability,我在读关于用C#创建不可变类的书。有人建议我这样做是为了实现真正的不变性: private readonly int id; public Person(int id) { this.id = id; } public int Id { get { return id; } } 我理解这一点,但是如果我想做一些错误检查,我怎么能抛出异常呢?考虑到下面的课程,我只能这样想: private readonly int id; private readonly string f

我在读关于用C#创建不可变类的书。有人建议我这样做是为了实现真正的不变性:

private readonly int id;

public Person(int id) {

    this.id = id;
}

public int Id {
    get { return id; }
}
我理解这一点,但是如果我想做一些错误检查,我怎么能抛出异常呢?考虑到下面的课程,我只能这样想:

private readonly int id;
private readonly string firstName;
private readonly string lastName;

public Person(int id,string firstName, string lastName)
{
    this.id=id = this.checkInt(id, "ID");
    this.firstName = this.checkString(firstName, "First Name");
    this.lastName = this.checkString(lastName,"Last Name");

}

private string checkString(string parameter,string name){

    if(String.IsNullOrWhiteSpace(parameter)){
        throw new ArgumentNullException("Must Include " + name);
    }

    return parameter;

}

private int checkInt(int parameter, string name)
{
    if(parameter < 0){

        throw new ArgumentOutOfRangeException("Must Include " + name);
    }
    return parameter;   
}
private只读int-id;
私有只读字符串名;
私有只读字符串lastName;
公共人物(int-id、string-firstName、string-lastName)
{
this.id=id=this.checkInt(id,“id”);
this.firstName=this.checkString(firstName,“First Name”);
this.lastName=this.checkString(lastName,“lastName”);
}
私有字符串检查字符串(字符串参数、字符串名称){
if(String.IsNullOrWhiteSpace(参数)){
抛出新ArgumentNullException(“必须包含”+名称);
}
返回参数;
}
私有int checkInt(int参数,字符串名称)
{
if(参数<0){
抛出新ArgumentOutOfRangeException(“必须包含”+名称);
}
返回参数;
}

这是正确的做法吗?若并没有,我将如何在不可变类中抛出异常?

若有必要将字段标记为只读,那个么除了在ctor中执行错误检查外,并没有其他选择。只读字段只能在构造函数或初始化块中修改

只需这样做:

public Person(int id) {
    if(id > 1200) throw new Exception(); // or put it in separate method
    this.Id = id;
}
您甚至可以进一步使用通用方法,如:

private void CheckValue<T>(T parameter, string name)
{
       if(T is int) if((parameter as int) < 0) throw new ArgumentOutOfRangeException(name));
       if(T is string) if(String.IsEmptyOrWhiteSpace(parameter as string)) throw new ArgumentNullException(name));
      //   ... if (T is) ... other types here
   }
}

我将内联这两个私有方法:

  private readonly int id;
  private readonly string firstName;
  private readonly string lastName;

  public Person(int id, string firstName, string lastName)
  {
     if(String.IsNullOrWhiteSpace(firstName))
        throw new ArgumentNullException("firstName");
     if(String.IsNullOrWhiteSpace(lastName))
        throw new ArgumentNullException("lastName");
     if(id < 0)
        throw new ArgumentOutOfRangeException("id");


     this.id=id;
     this.firstName = firstName ;
     this.lastName = lastName ;
  }
private只读int-id;
私有只读字符串名;
私有只读字符串lastName;
公共人物(int-id、string-firstName、string-lastName)
{
if(String.IsNullOrWhiteSpace(firstName))
抛出新的ArgumentNullException(“firstName”);
if(String.IsNullOrWhiteSpace(lastName))
抛出新ArgumentNullException(“lastName”);
if(id<0)
抛出新ArgumentOutOfRangeException(“id”);
这个.id=id;
this.firstName=firstName;
this.lastName=lastName;
}

如果使用私有setter,则可以通过反射设置属性,因此它不是不可变的。在本例中,这可能是问题,也可能不是问题。就我个人而言,我更喜欢你的方法,因为它是绝对不变的,其他程序员可以很容易地推断出意图

在任何情况下,由于您的数据是不可变的,所以您的错误也是不可变的,所以永远不会更改。你所遵循的模式是正确的

检查构造函数中的错误是正确的,因为您不希望创建带有错误的不可变对象

[我本想将此作为评论而不是回答,但我的声誉差了1分。我现在就把它放在这里。]

C#6支持不可变的仅getter自动属性,因此如果您使用Visual Studio 2015,您可以像这样进一步重构:

  public int Id { get; }

  public Person(int id, string firstName, string lastName)
  {
     if (id < 0)
        throw new ArgumentOutOfRangeException("id");

     Id=id;
  }
public int Id{get;}
公共人物(int-id、string-firstName、string-lastName)
{
if(id<0)
抛出新ArgumentOutOfRangeException(“id”);
Id=Id;
}

我仅将示例限制为
ID
属性。构造函数验证该值并将其分配给属性(如果有效),该属性是完全不可变的,而且您不必显式声明支持字段。这在。

@kbird上的重构演示中得到了很好的证明-我知道这一点,但在我提供的链接中,我被告知它不是真的不可变的,因为在类内您仍然可以调用私有setter。这是一个错误的概念不可变意味着什么,不可变意味着客户端类(外部的类)应该能够修改您的属性。(不是类本身内部)@Enigmativity-immutable只是意味着对象不能被更改(在创建之后)
GetHashCode
Equals
依赖于不可变属性,但不是相反。您可以拥有不可变的属性或类,而无需(显式或隐式)调用这两个方法中的任何一个。@kbird-私有setter比只读备份字段更糟糕,因为客户端类始终可以通过reflection@ThomasSchremser:这里很有趣。客户端类也可以通过反射更改私有只读字段。反射做大多数事情(TM)。如果这真的是更好的实践,那么在我提供的链接中,答案是将变量设为只读并使用构造函数?你是对的,但是如果你的字段是只读的,它只能从构造函数中更改,并且没有其他方法可以像你那样做(这是正确的方法),所以,我在我的示例中提供的是可以的吗?正确吗?根据我的品味和观点,我会在构造函数中使用if语句,但除此之外,是的。这取决于。如果你希望你的类是100%不可变的,那么这是正确的,是的。我想说的是,私有setter的问题不是反射,而是后来有人加入到你的类中并使用私有setter。实际上,对于私有的只读字段,您仍然会遇到同样的问题-反射可以让您了解这个问题讨论的大部分内容:谢谢,所以我的特殊情况是,我用于检查的私有方法可以吗?对吗?是的,如果这些是验证的简单示例,在现实生活中可能会发生变化/变得更复杂。但如果它们只是一句话,在现实生活中,我同意奥米卡德的观点,并且会把它们放在一起。公共静态bool IsNullOrWhiteSpace不是私有的,而是类String的公共静态方法,您在这里所做的只是从ctor而不是在方法中调用它
  public int Id { get; }

  public Person(int id, string firstName, string lastName)
  {
     if (id < 0)
        throw new ArgumentOutOfRangeException("id");

     Id=id;
  }