更改不可变类型的属性

Bea*_*ker 4 c# clone immutability icloneable

我已将不可变类型存储在临时 CQRS 读取存储中(查询/读取端,实际上是通过带有抽象访问层的简单列表实现的,此时我不想使用完整的文档数据库)。这些阅读商店包含如下项目:

public class SomeItem
{
    private readonly string name;
    private readonly string description;

    public SomeItem(string name, string description)
    {
        this.name = name;
        this.description = description;
    }

    public string Name
    {
        get { return this.name; }
    }

    public string Description
    {
        get { return this.description; }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我想更改名称并在第二个命令中更改描述。这些更改应保持当前状态,这意味着对于上面的示例:

// initial state
var someItem = new SomeItem("name", "description");

// update name -> newName
someItem = new SomeItem("newName", someItem.Description);

// update description -> newDescription
someItem = new SomeItem(someItem.Name, "newDescription");
Run Code Online (Sandbox Code Playgroud)

如果您有多个属性,这对我来说确实容易出错……您必须设法保持当前状态。我可以向每种类型添加类似 Clone() 的东西,但我认为/希望有更好的东西,性能良好且易于使用,我不想编写太多重复的代码(懒惰的程序员)。对于如何改进上面的代码有什么建议吗?SomeItem 类需要保持不可变(通过多个不同的线程传输)。

Lua*_*aan 6

遗憾的是,C# 中没有简单的方法。F# 有with关键字,您可以看一下 Lenses,但在 C# 中这一切都有点乏味。我能给你的最好的就是这样的:

class SomeItem
{
  private readonly string name;
  private readonly string description;

  public SomeItem(string name, string description)
  {
    this.name = name;
    this.description = description;
  }

  public SomeItem With
    (
      Option<string> name = null,
      Option<string> description = null
    )
  {
    return new SomeItem
      (
        name.GetValueOrDefault(this.name), 
        description.GetValueOrDefault(this.description)
      );
  }
}
Run Code Online (Sandbox Code Playgroud)

这允许您进行更新,例如

var newItem = oldItem.With(name: "My name!");
Run Code Online (Sandbox Code Playgroud)

我已经将这种方法与扩展方法和 T4 一起使用,效果非常好,但即使您手动编写代码,它也相当可靠 - 如果您添加新字段,则也必须将其添加到 中With,因此它工作得很好。

如果您愿意容忍运行时代码生成并降低类型安全性,还有其他几种方法,但在我看来,这有点违背原则。


Ess*_*set 6

在 C#9 中,我们使用了with运算符来实现此目的。

\n
   public record Car\n    {\n        public string Brand { get; init; }   \n        public string Color { get; init; }    \n    }\n    var car = new Car{ Brand = "BMW", Color = "Red" }; \n    var anotherCar = car with { Brand = "Tesla"};\n
Run Code Online (Sandbox Code Playgroud)\n
\n

With 表达式 在处理不可变数据时,常见模式是\n从现有值创建新值来表示新状态。例如,如果我们的人要更改他们的姓氏,我们会将其表示为一个新对象,该新对象是旧对象的副本,除非姓氏不同。此技术通常称为\n非破坏性突变。该记录不是代表一段时间内的人,而是代表给定时间的人\xe2\x80\x99s 状态。为了帮助这种编程风格,记录允许一种新的表达式;with 表达式:

\n
\n

C#9 中的新闻

\n

注意\nWith 运算符仅受记录支持。

\n
\n

记录经典面向对象编程的核心思想是对象具有强身份并封装随时间演变的可变状态。C# 在这方面一直很有效,但\n有时你想要的几乎完全相反,而这里 C#\xe2\x80\x99s\n默认值往往会成为障碍,使事情变得非常费力。

\n
\n

C#9 中的记录

\n