在不可变对象上创建“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"方法?
这当然可以做到,并且是在包括实体框架在内的多个地方使用的方法。在 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# 可能根本无法编译的一些细微差异方面也可能更有用。