我正在用c编写函数式代码。我的许多类都是不可变的,使用方法返回实例的修改副本。
例如:

sealed class A
{
    readonly X x;
    readonly Y y;

    public class A(X x, Y y)
    {
        this.x = x;
        this.y = y;
    }

    public A SetX(X nextX)
    {
        return new A(nextX, y);
    }

    public A SetY(Y nextY)
    {
        return new A(x, nextY);
    }
}

这是一个很小的例子,但是想象一个更大的类,有更多的成员。
问题是构造这些修改过的副本非常冗长。大多数方法只更改一个值,但我必须将所有未更改的值传递到构造函数中。
在用修饰符方法构造不可变类时,是否有一种模式或技术可以避免所有这些问题?
注意:我不想为reasons discussed elsewhere on this site使用struct
更新:我发现这在f_中被称为“复制和更新记录表达式”。

最佳答案

对于更大的类型,我将构建一个With函数,该函数的参数在未提供时都默认为null

public sealed class A
{
    public readonly X X;
    public readonly Y Y;

    public A(X x, Y y)
    {
        X = x;
        Y = y;
    }

    public A With(X X = null, Y Y = null) =>
        new A(
            X ?? this.X,
            Y ?? this.Y
        );
}

然后使用c的命名参数特性,因此:
val = val.With(X: x);

val = val.With(Y: y);

val = val.With(X: x, Y: y);

我发现int比许多setter方法更有吸引力。这确实意味着null变成了一个不可用的值,但是如果你走的是功能路线,那么我假设你也在试图避免null并使用选项。
如果有值类型/结构作为成员,则在Nullable中使它们With,例如:
public sealed class A
{
    public readonly int X;
    public readonly int Y;

    public A(int x, int y)
    {
        X = x;
        Y = y;
    }

    public A With(int? X = null, int? Y = null) =>
        new A(
            X ?? this.X,
            Y ?? this.Y
        );
}

注意,这不是免费的,每次调用N都有With空比较操作,其中N是参数的数目。我个人认为这种方便值得付出代价(最终可以忽略不计),但是如果您有任何对性能特别敏感的东西,那么您应该回到定制的setter方法。
如果您觉得编写With函数太单调,那么可以使用myopen-source C# functional programming library: language-ext。可以这样做:
[With]
public partial class A
{
    public readonly int X;
    public readonly int Y;

    public A(int x, int y)
    {
        X = x;
        Y = y;
    }
}

您必须在项目中包含LanguageExt.CoreLanguageExt.CodeGenLanguageExt.CodeGen不需要包含在项目的最终版本中。

09-05 13:16
查看更多