bra*_*ing 14 c# f# immutability
例如在F#中我们可以定义
type MyRecord = {
X: int;
Y: int;
Z: int
}
let myRecord1 = { X = 1; Y = 2; Z = 3; }
Run Code Online (Sandbox Code Playgroud)
并且我可以做更新
let myRecord2 = { myRecord1 with Y = 100; Z = 2 }
Run Code Online (Sandbox Code Playgroud)
这很棒,而且记录自动实现IStructuralEquality而不需要额外的努力这一事实让我希望在C#中实现这一点.但是,也许我可以在F#中定义我的记录,但仍然可以在C#中执行一些更新.我想像一个API
MyRecord myRecord2 = myRecord
.CopyAndUpdate(p=>p.Y, 10)
.CopyAndUpdate(p=>p.Z, 2)
Run Code Online (Sandbox Code Playgroud)
有没有办法,我不介意脏黑客,如上所述实施CopyAndUpdate?CopyAndUpdate的C#签名将是
T CopyAndUpdate<T,P>
( this T
, Expression<Func<T,P>> selector
, P value
)
Run Code Online (Sandbox Code Playgroud)
它可以做到,但正确地做这件事会非常困难(这肯定不适合我的答案).以下简单实现假定您的对象只具有读写属性和无参数构造函数:
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
这略微违背了这一点,因为您可能希望在不可变类型上使用它 - 但是您总是必须使用所有参数调用构造函数,并且不清楚如何链接构造函数参数(当您创建实例时)您可以阅读的属性.
该With方法创建一个新实例,复制所有属性值,然后设置要更改的属性(使用PropertyInfo从表达式树中提取的 - 不进行任何检查!)
public static T With<T, P>(this T self, Expression<Func<T, P>> selector, P newValue)
{
var me = (MemberExpression)selector.Body;
var changedProp = (System.Reflection.PropertyInfo)me.Member;
var clone = Activator.CreateInstance<T>();
foreach (var prop in typeof(T).GetProperties())
prop.SetValue(clone, prop.GetValue(self));
changedProp.SetValue(clone, newValue);
return clone;
}
Run Code Online (Sandbox Code Playgroud)
以下演示符合预期,但正如我所说,它有很多限制:
var person = new Person() { Name = "Tomas", Age = 1 };
var newPerson = person.With(p => p.Age, 20);
Run Code Online (Sandbox Code Playgroud)
一般来说,我认为使用像With这里的基于通用反射的方法可能不是一个好主意,除非你有足够的时间来正确实现它.With对于您使用的每个类型实现一个方法可能更容易,该方法接受可选参数并将其值设置为克隆值(手动创建)(如果值不是)null.签名将是这样的:
public Person With(string name=null, int? age=null) { ... }
Run Code Online (Sandbox Code Playgroud)
您可以使用可选参数实现类似的操作:
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);
Run Code Online (Sandbox Code Playgroud)
这实际上非常接近F#为记录生成的代码.