在immutables上创建一个“with”方法

时间:2014-11-17 21:43:02

标签: c# reflection lambda immutability

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

2 个答案:

答案 0 :(得分:2)

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

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

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

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

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

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

答案 1 :(得分:0)

因为F#和C#都是.NET语言并因此编译为相同的IL,所以您还可以考虑F#如何在其中工作。有几种方法可以看到这个 -

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

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