如何在C#中创建不可变对象?

31 c# validation immutability entity-framework-4

在关于C#模式验证的最佳实践的问题中,最高投票的答案是:

我倾向于在构造函数中执行所有验证.这是必须的,因为我几乎总是创建不可变对象.

你究如何在C#中创建一个不可变对象?你刚才用readonly关键字吗?

如果要在Entity Framework生成的模型类的构造函数中进行验证,这究竟是如何工作的?

它看起来像下面?

public partial readonly Person
{
    public Person()
}
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 68

这里有趣的问题是你的评论中的问题:

你有什么样的对象,你不需要在某些时候修改值?我猜不是模特课,对吗?我不得不在我的数据库中更改一个人的名字 - 这不符合这个想法.

好吧,考虑已经不可改变的事情.数字是不可变的.一旦你有12号,它就是12.你无法改变它.如果您有一个包含12的变量,您可以将变量的内容更改为13,但是您要更改变量,而不是数字12.

与字符串相同."abc"是"abc",它永远不会改变.如果您有一个包含"abc"的变量,您可以将其更改为"abcd",但这不会更改"abc",这会更改变量.

列表怎么样?{12,"abc"}是12后跟"abc"的列表,该列表永远不会改变.列表{12,"abcd"}是不同的列表.

事情就这样发生了.因为在C#中你可以这样做.如果允许列表在不改变其标识的情况下改变其内容,则可以说这两个列表之间存在引用标识.

当你谈到"模特"时,你就钉在了头上.你在建模变化的东西吗?如果是这样,那么使用更改的类型对其进行建模可能是明智的.这样做的好处是模型的特征与正在建模的系统相匹配.不利的一面是,做一些"回滚"功能变得非常棘手,你可以"撤消"一个变化.

也就是说,如果你将{12,"abc"}变为{12,"abcd"},然后想要回滚变异,你是怎么做到的?如果列表是不可变的,您只需保留两个值并选择您想要成为"当前"值的值.如果列表是可变的,那么你必须让撤销逻辑保持"撤销功能",它知道如何撤消突变.

至于您的具体示例,您当然可以创建一个不可变的数据库.如何更改不可变数据库中某人的姓名?你没有.您创建一个包含所需数据的数据库.使用不可变类型的技巧是有效地执行此操作,而无需复制数十亿个字节.不可变数据结构设计需要找到巧妙的方法来在两个几乎相同的结构之间共享状态.


Mar*_*ers 11

只读取所有字段是创建不可变对象的一个​​很好的步骤,但仅此一点是不够的.这是因为只读字段仍然可以是对可变对象的引用.

在C#中,编译器不强制实现immutability.你必须要小心.

  • ..有时你可以返回那些可变字段的不可变(或只读)版本,例如[`List <T> .AsReadOnly`](http://msdn.microsoft.com/en-us/library/e78dcd75 .aspx)为您的清单. (2认同)

Lad*_*nka 6

这个问题有两个方面:

  1. 实例化对象时的不可变类型
  2. EF实例化对象时的不可变类型

第一个方面需要这样的结构:

public class MyClass
{
  private readonly string _myString;
  public string MyString
  {
    get
    {
      return _myString;
    }
  }

  public MyClass(string myString)
  {
    // do some validation here

    _myString = myString;
  }
}
Run Code Online (Sandbox Code Playgroud)

现在的问题 - EF.EF需要无参数构造函数,EF必须在属性上具有setter.我在这里问了非常相似的问题.

您的类型必须如下所示:

public class MyClass
{
  private string _myString;
  public string MyString
  {
    get
    {
      return _myString;
    }
    private set
    {
      _myString = value;
    }
  }

  public MyClass(string myString)
  {
    // do some validation here

    _myString = myString;
  }

  // Not sure if you can change accessibility of constructor - I can try it later
  public MyClass()
  {}
}
Run Code Online (Sandbox Code Playgroud)

您还必须通知EF关于MyString属性的私有setter - 这是在EDMX文件中的enitity属性中配置的.显然,当EF将从DB实现对象时,将不会进行验证.此外,您将无法使用ObjectContext.CreateObject等方法(您将无法填充该对象).

实体对象T4模板和默认代码生成创建工厂方法CreateMyClass而不是带有参数的构造函数.POCO T4模板不生成工厂方法.

我没有先用EF Code试试这个.