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

时间:2011-02-23 21:20:21

标签: c# validation entity-framework-4 immutability

在关于Best practices for C# pattern validation的问题中,投票答案最高 表示:

  

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

如何在C#中创建不可变对象?您是否只使用readonly关键字?

如果要在Entity Framework生成的模型类的构造函数中进行验证,这究竟有用吗?

它看起来像下面的吗?

public partial readonly Person
{
    public Person()
}

8 个答案:

答案 0 :(得分:67)

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

  

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

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

与字符串相同。 “abc”是“abc”,它永远不会改变。如果你有一个包含“abc”的变量,你可以将它改为“abcd”,但这不会改变“abc”,这会改变变量。

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

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

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

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

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

答案 1 :(得分:10)

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

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

答案 2 :(得分:6)

答案 3 :(得分:6)

这个问题有两个方面:

  1. 实例化对象时的不可变类型
  2. EF实例化对象时的不可变类型
  3. 第一个方面需要这样的结构:

    public class MyClass
    {
      private readonly string _myString;
      public string MyString
      {
        get
        {
          return _myString;
        }
      }
    
      public MyClass(string myString)
      {
        // do some validation here
    
        _myString = myString;
      }
    }
    

    现在问题 - EF。 EF需要无参数构造函数,EF必须在属性上具有setter。我问过非常相似question here

    您的类型必须如下:

    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()
      {}
    }
    

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

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

    我没有先用EF Code尝试这个。

答案 4 :(得分:2)

不可变值对象是无法更改的值对象。你不能修改它的状态,你必须创建新的

查看Eric Lippert的博客:

种类的不变性 http://blogs.msdn.com/b/ericlippert/archive/2007/11/13/immutability-in-c-part-one-kinds-of-immutability.aspx

看看

Immutable object pattern in C# - what do you think?

答案 5 :(得分:1)

  

如果要在Entity Framework生成的模型类的构造函数中进行验证,这究竟有用吗?

它不适用于此上下文,因为EF要求实体类的属性是公共的,否则它无法实例化它。

但欢迎您在代码中进一步使用不可变对象。

答案 6 :(得分:1)

C#9提出了新功能名称作为Record。如果要使单个属性不变,则仅初始化属性非常有用。如果您希望整个对象是不可变的并且表现得像一个值,那么您应该考虑将其声明为记录:

public data class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

类声明中的data关键字将其标记为一条记录。

参考: https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/#records

答案 7 :(得分:0)

@Eric Lippert很好的评论,但除了回答问题:

  

您将拥有什么样的对象,而无需修改   某个时候的价值?我猜不是模型课,对吗?我有   必须在我的数据库中更改一个人的名字-这不适合   这个想法。

比方说,您的数据结构很大,您想查询其信息,但是它一直在变化。您需要某种锁定系统,以确保当有人将某物从一个地方存放到另一个地方时,不要说尝试计算系统中的总数。 (说一个仓库管理系统)

这很难做到,因为这些东西总是以意想不到的方式影响事物,数据在您的脚下不断变化。

如果在不更新大型数据结构时冻结了大数据结构,以致无法更改内存并且将其暂停在一致状态,该怎么办?现在,当您要再次更改它时,您必须将数据结构复制到一个新位置,并且它相当大,这是一个缺点,但是好处是您无需锁定任何内容,因为新的数据副本不再共享直到它被更新。这意味着任何人都可以随时读取数据结构的最新副本,从而完成复杂的工作。

如果您不想处理并发问题并且没有太多数据需要处理,那么是个非常有用的概念。 (例如,如果1MB的数据并以10 / sec的速度更新,则表示正在复制10MB的数据)