我们可以从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);