在不可变对象上创建“with”方法

本文关键字:with 方法 创建 不可变 对象 | 更新日期: 2023-09-27 18:30:38

>我想模仿 C# 中的 F# 'with' 关键字(可用于记录)。

现在,当我创建一个新的不可变类时,我只是手动添加一些自定义的"with"方法,如下所示:

public class MyClass
{
    public readonly string Name;
    public readonly string Description;
    public MyClass(string name, string description)
    {
        this.Name = name;
        this.Description = description;
    }
    // Custom with methods
    public MyClass WithName(string name)
    {
        return new MyClass(name, this.Description);
    }
    public MyClass WithDescription(string description)
    {
        return new MyClass(this.Name, description);
    }
}

对于我个人的 c# 开发,我尝试创建一个通用方法来执行此操作(在理想情况下,我会使用 F#)。我做的"最好的"是这样的:

    public static class MyExtensions
    {
        public static TSource With<TSource, TField>(
            this TSource obj, 
            string fieldName, 
            TField value) 
            where TSource : class
        {
            // Reflection stuff to use constructor with the new value 
            // (check parameters names and types)...
        }
    }

它可以工作,但我不太满意,因为我使用字符串参数丢失了编译时错误(现在我不关心性能问题)。

我真的很希望能够编写类似于以下代码的内容,其中我将字符串参数替换为"投影"lambda:

var myClass1 = new MyClass("name", "desc");
var myClass2 = myClass1.With(obj => obj.Name, "newName");

我的扩展方法看起来像:

    public static TSource With<TSource, TField>(
        this TSource obj, 
        Expression<Func<TSource, TField>> projection, 
        TField value)
        where TSource : class
    {
        // TODO
    }

所以这是我的问题:

  • 是否可以对投影结果使用反射并从中获取字段名称?
  • 其他人是否已经在 C# 中做了一个健壮的"with"方法?

在不可变对象上创建“with”方法

这当然可以做到,并且是在包括实体框架在内的多个地方使用的方法。在 EF 中,它用于在查询中包含属性。

DbContext.Categories.Include(c=>c.Products)

对此集的查询也会拉取类别中的产品。可以检查扩展方法中的表达式并提取成员信息,如下所示

((System.Linq.Expressions.MemberExpression)projection.Body).Member

当然,在实践中,您需要一些错误处理来确保表达式是成员表达式。然后,可以使用反射来设置相应的属性。您将需要一个 setter 才能正常工作,但您可以通过反射访问私有 setter,因此类型仍然可以有效地不可变。这不是一个完美的解决方案,但我想它已经足够接近了。

我不知道这种方法的任何现有实现。我很想看看你的完整版本。

由于 F# 和 C# 都是 .NET 语言,因此可以编译到同一个 IL,因此还可以考虑 F# 如何在后台完成其工作。有几种方法可以看到这一点-

  • 编译一些简单的 F# 代码,然后使用 IL to cdoe 工具(如 DotPeek)将其反编译为 C#。
  • 编译一些 F# 代码,并使用 IL 检查工具(如 ILDasm)对其进行检查

尽管第二个选项需要一些 IL 的工作知识,但它在查看 C# 可能根本无法编译的一些细微差异方面也可能更有用。