c# - C# 中的通用不可变类

标签 c# oop design-patterns functional-programming immutability

我在 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);
    }
}

这是一个微不足道的例子,但想象一个更大的类(class),有更多的成员。

问题是构建这些修改后的副本非常冗长。大多数方法只更改一个值,但我必须将所有 未更改的值传递给构造函数。

在使用修饰符方法构造不可变类时,是否有一种模式或技术可以避免所有这些样板代码?

注意:我不想为 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 并使用选项。

如果您将值类型/结构作为成员,则在 With 中将它们设为 Nullable,例如:

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
        );
}

但是请注意,这不是免费的,每次调用 With 都会有 N 次空值比较操作,其中 N 是数字的论据。我个人认为这种便利值得付出代价(最终可以忽略不计),但是如果您有任何对性能特别敏感的东西,那么您应该回退到定制的 setter 方法。

如果你觉得编写 With 函数太乏味了,那么你可以使用我的 open-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 不需要包含在项目的最终版本中。

最后一点便利来自 [Record] 属性:

[Record]
public partial class A
{
    public readonly int X;
    public readonly int Y;
}

它将构建 With 函数,以及您的构造函数、解构函数、结构相等性、结构排序、镜头、GetHashCode 实现、ToString 实现和序列化/反序列化。

Here's an overview of all of the Code-Gen features

关于c# - C# 中的通用不可变类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38575646/

相关文章:

c# - 是否可以结合两个条件

language-agnostic - 对接口(interface)编程,通用性有多通用?

design-patterns - UML类图中伪代码注解对应的是什么

.net - 是否可以在运行时将接口(interface)分配给对象?

css - 如何更改图案中给定背景图像之间的空间。图片为在线必填

c# - 无重叠捕获

c# - 套接字,接收序列化异常

具有继承性的 Python 循环导入

c# - 我是否需要将 Azure 函数作为异步方法运行?

php - 与界面交互?