如何有效地“改变”不可变对象?

时间:2015-10-12 12:03:28

标签: c# immutability

是否有任何聪明的设计模式或用于“修改”不可变对象的通用方法?

背景:
让我们有一组不同的(没有共同的base)不可变对象,每个对象具有不同的{get; private set;}属性集和一个公共构造函数(接受一组属性值)。这些对象是并且应该保持不变,但有时候,在某种特殊模式下,它们的值必须“改变”。

这种修改意味着只创建具有与原始对象相同值的新对象,但更新的属性除外(如o = new c(o.a, updateB, updateC, o.d, ...))。

我可以想象调用构造函数,或者定义一个(例如扩展)方法返回新实例,接受一些参数来识别更新的属性并通过反射修改它们/但它们似乎都是非常具体的对于“任何”不可变的系统范围使用不优雅。我喜欢linq处理的方式,例如IEnumerable和您可以生成的链并完全重新转换输入集合。有什么想法吗?

1 个答案:

答案 0 :(得分:3)

我所知道的一个类似的例子是Roslyn,微软目前的C#编译器,它广泛使用不可变数据结构。语法树类为每个属性提供了方便的方法,用于返回只更改了一个属性的新实例,例如

var cu = Syntax.CompilationUnit()
            .AddMembers(
                Syntax.NamespaceDeclaration(Syntax.IdentifierName("ACO"))
                        .AddMembers(
                        Syntax.ClassDeclaration("MainForm")
                            .AddBaseListTypes(Syntax.ParseTypeName("System.Windows.Forms.Form"))
                            .WithModifiers(Syntax.Token(SyntaxKind.PublicKeyword))
                            .AddMembers(
                                Syntax.PropertyDeclaration(Syntax.ParseTypeName("System.Windows.Forms.Timer"), "Ticker")
                                        .AddAccessorListAccessors(
                                        Syntax.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(Syntax.Token(SyntaxKind.SemicolonToken)),
                                        Syntax.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(Syntax.Token(SyntaxKind.SemicolonToken))),
                                Syntax.MethodDeclaration(Syntax.ParseTypeName("void"), "Main")
                                        .AddModifiers(Syntax.Token(SyntaxKind.PublicKeyword))
                                        .AddAttributes(Syntax.AttributeDeclaration().AddAttributes(Syntax.Attribute(Syntax.IdentifierName("STAThread"))))
                                        .WithBody(Syntax.Block())
                                )
                        )
                );

或在你的情况下:

o = o.WithB(updateB).WithC(updateC);

我认为对于预期的用例非常好(只更新一些属性,同时保持其他所有内容相同)。当有许多属性时,它尤其胜过»总是必须调用ctor«方法。

在Roslyn中,我认为这是自动生成的。如果有必要,你可以用T4做类似的事情。