C# 如何";更改“;不可变对象是否有效?

C# 如何";更改“;不可变对象是否有效?,c#,immutability,C#,Immutability,有没有聪明的设计模式或某种通用方法来“修改”不可变对象 背景: 让我们有一组不同的(没有公共的base)不可变对象,每个对象都有一组不同的{get;private set;}属性和一个公共构造函数(接受一组属性值)。这些对象是不变的,并且应该保持不变,但有时,在某种特殊模式下,它们的值需要“更改” 这种修改意味着只需创建与原始对象具有相同值的新对象,但更新的属性除外(如o=newc(o.a,updateB,updateC,o.d…)) 我可以想象在适当的位置调用构造函数,或者定义一个(例如扩展)

有没有聪明的设计模式或某种通用方法来“修改”不可变对象

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

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


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

我知道的一个类似的例子是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«方法


在罗斯林,我想这都是自动生成的。如果有保证的话,您可能可以对T4执行类似的操作。

感谢T4注释,我不知道这个工具!源代码在使用时会读起来很糟糕,但至少它与VisualStudio集成得很好。对于自动生成的代码,我也使用了其他方法,这些方法需要在更改生成脚本后执行单独的生成步骤或手动重新生成源文件。