【问题标题】:Create a "with" method on immutables在不可变对象上创建一个“with”方法
【发布时间】:2014-11-17 21:43:02
【问题描述】:

我想模仿 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”方法?

【问题讨论】:

  • 我已经编辑了你的标题。请参阅“Should questions include “tags” in their titles?”,其中的共识是“不,他们不应该”。
  • 第一个问题,请参阅stackoverflow.com/questions/671968/…。恐怕你的第二个并不是 StackOverflow 的主题。
  • 你的Expression 仍然没有强制类型安全。有人仍然可以通过With(obj =&gt; "foo", "bar") 之类的操作来调用它,而您没有真正的方法来处理它。
  • 最好的办法是为这些不可变对象创建构建器对象。具有相同类型的可变版本,以及在两者之间来回移动的转换方法/运算符。然后,您可以获取一个对象,将其转换为构建器对象,设置一些值,然后将其转换回来。

标签: c# reflection lambda immutability


【解决方案1】:

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

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

对这个集合的查询也会拉取一个类别中的产品。您可以检查扩展方法中的表达式并像这样提取成员信息

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

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

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

【讨论】:

  • 谢谢,我会在我的实施结束后编辑这篇文章。
【解决方案2】:

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

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

虽然第二个选项需要一些 IL 的工作知识,但它也可能更有助于发现 C# 可能根本无法编译的一些细微差异。

【讨论】:

  • "with" 是 F# 编译器已知的语言关键字。我很确定我将看到的唯一 IL 代码“只是”一个构造函数调用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-11
  • 2020-12-06
  • 2018-03-28
相关资源
最近更新 更多