是否可以实现一种克隆对象并将更改应用于私有属性的方法(无需反射)?

Is it possible to implement a method that clones an object and applies changes to private properties (without reflection)?

DDD 中的值对象是不可变的,属性通常通过构造函数设置一次。

有时我需要一个值对象的副本,只有一些更改,例如应复制 10 个属性,其中一个 属性 将获得新值。在这种情况下,我想避免使用带有 11 个参数的构造函数,而是想实现一个 returns 副本但同时对属性应用一些更改的方法。我知道我可以通过反射来做到这一点,但想检查一下是否可以避免反射。

public class Foo
{
    public int Bar { get; set; } // actual use case is { get; private set; }
    
    public Foo(int bar)
    {
        Bar = bar;
    }
    
    public Foo CloneAndApply(Action<Foo> apply)
    {
        var result = new Foo(Bar);
        apply(result);
        return result;
    }
}

之所以有效,是因为“栏”属性 有一个 public setter。我需要一些允许在私人成员时设置“酒吧”的东西。

var test = new Foo(1);
var clone = test.CloneAndApply(x => x.Bar = 2);
Console.WriteLine(clone.Bar);

您可能对 Records, introduced in C# 9. Records contain many parts, but one of them is support for "withers" 感兴趣,它与新的 init-only 属性交互,可以轻松创建记录的修改副本。

记录支持诸如主构造函数之类的东西,我将在这里略过。简单来说,您的记录可能如下所示:

public record Foo
{
    public int Bar { get; init; }
    
    public Foo(int bar)
    {
        Bar = bar;
    }
}

并可用作:

var foo = new Foo(3);
var foo2 = foo with { Bar = 4 };

此记录还根据其各个成员的相等性自动实现相等性,并覆盖 ToString 实现。

长期计划是允许将 wither 用于非记录类型,尽管(从 C# 9 开始)这还不受支持。


如果你利用主构造器,你可以写得更简洁:

public record Foo(int Bar); 

这会自动生成 Bar 属性,带有 getinit 访问器,以及一个分配给它的构造函数。

另一个解决方案可能是反射。这也可用于为私有属性赋值:

    class Program
    {
        static void Main(string[] args)
        {
            var test = new Foo(1);
            var clone = test.CloneAndApply(x => x.GetType().GetProperty("Bar").SetValue(x, 2));
            Console.WriteLine(clone.Bar);
        }
    }

    public class Foo
    {
        public int Bar { get; private set; }

        public Foo(int bar)
        {
            Bar = bar;
        }

        public Foo CloneAndApply(Action<Foo> apply)
        {
            var result = new Foo(Bar);
            apply(result);
            return result;
        }
    }

我使用 WithX 方法实现了 .net5 之前的记录,这些方法传递了需要不同的 属性。不花哨,但很管用。

public class Foo
{
    public int X { get; private set; }
    
    public int Bar { get; private set; }
    
    public Foo(int x, int bar) { X = x; Bar = bar; }
    
    public Foo WithBar(int bar) => new Foo(X, bar);
    }
}