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

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)

Tom*_*cek 9

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

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)


Dan*_*iel 7

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

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#为记录生成的代码.