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

C# 我们可以从c访问F复制和更新功能吗?,c#,f#,immutability,C#,F#,Immutability,例如,在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中

例如,在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=>p.Y, 10)
    .CopyAndUpdate(p=>p.Z, 2)
有没有一种方法,而且我不介意肮脏的黑客,实现上述CopyAndUpdate?CopyAndUpdate的C#符号是

T CopyAndUpdate<T,P>
   ( this T
   , Expression<Func<T,P>> selector
   , P value
   )
T复制和更新
(这不是
,表达式选择器
,P值
)

这是可以做到的,但正确地做到这一点将是相当困难的(这肯定不符合我的答案)。以下简单实现假定对象只有读写属性和无参数构造函数:

class Person
{
  public string Name { get; set; }
  public int Age { get; set; }
}
这稍微有点违背了这一点,因为您可能希望在不可变类型上使用它,但是您必须始终使用所有参数调用构造函数,并且不清楚如何将构造函数参数(当您创建实例时)与您可以读取的属性相链接

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

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

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

您可以使用可选参数实现类似的功能:

类MyRecord{
公共只读int X;
公共只读信息;
公共只读int Z;
公共MyRecord(整数x,整数y,整数z){
X=X;Y=Y;Z=Z;
}
公共MyRecord(MyRecord原型,int?x=null,int?y=null,int?z=null)
:这个(x??prototype.x,y??prototype.y,z??prototype.z){}
}
var rec1=新的MyRecord(1,2,3);
var rec2=新的MyRecord(rec1,y:100,z:2);

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

如果其他人在这方面遇到了障碍,我最近需要做同样的事情,并且能够接受@Tomas Petricek的答案,并将其扩展到处理不可变记录:

    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 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#来定义记录,这样我就可以免费获得IStructuralEquality和IComparable。我必须在C#中手动实现它。我将用一些F#记录测试您的代码,看看它是否有效。Tomas做得很好,但在C#中,它远远不能让myRecord2={myRecord1 with Y=100;Z=2}是的,F#记录具有只读属性,但它们具有可预测(我认为)名称的可变字段。因此,您必须更改克隆代码以迭代字段(这应该很容易,但需要获取私有字段),然后从表达式树中获取的属性名“猜测”字段名。(检查记录是如何编译的-应该有一些命名方案可以工作)。抱歉,目的是使用F#记录,因为它们自动实现IStructualQuality和IComparable。我想使用C中的F记录。也许是个大问题,但我已经厌倦了填写所有的样板文件。对不起。我以为你想用C#复制记录。如果有人感兴趣,我会留下我的答案。我认为这是一个明智的答案(我的+1)。一个带有可选参数的
方法可以作为扩展方法添加到F#记录中(更多的写入,但它会更快更好:-),但是对于可为空的引用类型,这有一些微妙的错误。如果“null”是该类型的有效值,那么就不可能更新具有null值的类型,因为无法区分“this was passed as null”和“this was not passed in”,这可能很有用:可能是
public Person With(string name=null, int? age=null) { ... }
    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 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);