我们可以从c#访问F#复制和更新function吗?

例如在F#中我们可以定义

type MyRecord = { X: int; Y: int; Z: int } let myRecord1 = { X = 1; Y = 2; Z = 3; } 

并且我可以做更新

 let myRecord2 = { myRecord1 with Y = 100; Z = 2 } 

这很棒,而且记录自动实现IStructuralEquality而不需要额外的努力这一事实让我希望在C#中实现这一点。 但是,也许我可以在F#中定义我的记录,但仍然可以在C#中执行一些更新。 我想像一个API

 MyRecord myRecord2 = myRecord .CopyAndUpdate(p=>pY, 10) .CopyAndUpdate(p=>pZ, 2) 

有没有办法,我不介意脏黑客,如上所述实施CopyAndUpdate? CopyAndUpdate的C#签名将是

 T CopyAndUpdate ( this T , Expression<Func> selector , P value ) 

它可以做到,但正确地做这件事会非常困难(这肯定不适合我的回答)。 以下简单实现假定您的对象只具有读写属性和无参数构造函数:

 class Person { public string Name { get; set; } public int Age { get; set; } } 

这略微违背了这一点,因为您可能希望在不可变类型上使用它 – 但是随后您必须使用所有参数调用构造函数,并且不清楚如何链接构造函数参数(当您创建实例时)您可以阅读的属性。

With方法创建一个新实例,复制所有属性值,然后设置要更改的属性值(使用从表达式树中提取的PropertyInfo – 不进行任何检查!)

 public static T With(this T self, Expression> selector, P newValue) { var me = (MemberExpression)selector.Body; var changedProp = (System.Reflection.PropertyInfo)me.Member; var clone = Activator.CreateInstance(); foreach (var prop in typeof(T).GetProperties()) prop.SetValue(clone, prop.GetValue(self)); changedProp.SetValue(clone, newValue); return clone; } 

以下演示按预期运行,但正如我所说,它有很多限制:

 var person = new Person() { Name = "Tomas", Age = 1 }; var newPerson = person.With(p => p.Age, 20); 

一般来说,我认为使用像这样的基于通用reflection的方法可能不是一个好主意,除非你有足够的时间来正确实现它。 对于您使用的每个类型实现一个With方法可能更容易, With方法接受可选参数并将其值设置为克隆值(手动创建),如果该值不为null 。 签名将是这样的:

 public Person With(string name=null, int? age=null) { ... } 

您可以使用可选参数实现类似的操作:

 class MyRecord { public readonly int X; public readonly int Y; public readonly int Z; public MyRecord(int x, int y, int z) { X = x; Y = y; Z = z; } public MyRecord(MyRecord prototype, int? x = null, int? y = null, int? z = null) : this(x ?? prototype.X, y ?? prototype.Y, z ?? prototype.Z) { } } var rec1 = new MyRecord(1, 2, 3); var rec2 = new MyRecord(rec1, y: 100, z: 2); 

这实际上非常接近F#为记录生成的代码。

如果其他人绊倒了这个,我最近需要做同样的事情并且能够接受@Tomas Petricek的答案并扩展它以使用不可变记录:

  public static T With(this T self, Expression> selector, P newValue) { var me = (MemberExpression)selector.Body; var changedProp = (System.Reflection.PropertyInfo)me.Member; var constructor = typeof(T).GetConstructors()[0]; var parameters = constructor.GetParameters().Select(p => p.Name); var properties = typeof(T).GetProperties(); var args = parameters .Select(p => properties.FirstOrDefault(prop => String.Equals(prop.Name,p, StringComparison.CurrentCultureIgnoreCase))) .Select(prop => prop == changedProp ? newValue : prop.GetValue(self)) .ToArray(); var clone = (T) constructor.Invoke(args); return clone; } 

用法

 // F# type Person = { Name : string Age : int } // C# var personRecord = new Person("John",1); var newPerson = personRecord.With(p => p.Age, 20);