更改不可变类型的属性

时间:2016-08-05 12:16:21

标签: c# clone immutability icloneable

我已将不可变类型存储在临时CQRS读取存储(查询/读取方面,实际上由具有抽象访问层的简单List实现,此时我不想使用完整的文档数据库)。这些读取存储包含以下项目:

xxx.com/blog

现在我要更改名称,并在第二个命令中更改说明。 这些更改应该保持当前状态,这意味着上面的示例:

yyy.com/

如果您有多个属性,这看起来很容易让我感觉...您必须管理保持当前状态。我可以为每种类型添加类似Clone()的东西,但我认为/希望有更好的东西表现良好且易于使用,我不想写很多重复代码(懒惰的程序员)。有什么建议如何改进上面的代码? SomeItem类需要保持不变(通过几个不同的线程传输)。

4 个答案:

答案 0 :(得分:4)

可悲的是,在C#中没有简单的方法。 F#有with关键字,你可以看一下镜头,但在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)
      );
  }
}

这允许您进行

等更新
var newItem = oldItem.With(name: "My name!");

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

如果您愿意容忍运行时代码生成并降低类型安全性,那么还有一些方法,但这有点违背谷物IMO。

答案 1 :(得分:2)

在 C#9 中,我们获得了用于此目的的 with 运算符。

   public record Car
    {
        public string Brand { get; init; }   
        public string Color { get; init; }    
    }
    var car = new Car{ Brand = "BMW", Color = "Red" }; 
    var anotherCar = car with { Brand = "Tesla"};
<块引用>

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

News in C#9

注意 仅记录支持 with 运算符。

<块引用>

记录 经典面向对象编程的核心思想是对象具有强身份并封装可变状态 随着时间的推移而发展。 C# 在这方面一直很有效,但是 有时你想要的恰恰相反,这里是 C# 默认值往往会妨碍工作,使事情变得非常费力。

Recods in C#9

答案 2 :(得分:1)

您正在寻找的通常称为 with operator

// returns a new immutable object with just the single property changed
someItem = { someItem with Name = "newName" };

不幸的是,与F#不同,C#没有这样的运营商(但是?)。

其他C#开发人员也缺少此功能,这就是someone wrote a Fody extension to do exactly that

的原因

这是另一种方法,它手动实现UpdateWith方法但需要Option<T>辅助类。 Luaan's answer更详细地描述了这种方法:

答案 3 :(得分:-2)

如果您想要做的是(如您所评论的那样)更新现有对象的名称,则readonly属性可能是错误的设计。 否则,如果它真的是你想要创建的新对象,你可能希望你的类用'Dispose'方法实现一些接口。