我想在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
}
所以这是我的问题:
答案 0 :(得分:2)
这当然可以完成,并且是在包括实体框架在内的多个地方使用的方法。在EF中,它用于在查询中包含属性。
DbContext.Categories.Include(c=>c.Products)
对此集合的查询也会将产品拉入类别中。您可以检查扩展方法中的表达式,并像这样提取成员信息
((System.Linq.Expressions.MemberExpression)projection.Body).Member
当然在实践中你需要一些错误处理来确保表达式是一个成员表达式。然后,您可以使用反射来设置适当的属性。你需要一个setter才能工作,但你可以通过反射访问私有的setter,这样类型仍然可以有效地不变。这不是一个完美的解决方案,但我想它已足够接近了。
我不知道这种方法的任何现有实施。我很想看到你的完整版。
答案 1 :(得分:0)