8

F# 有一个方便的特性“with”,例如:

type Product = { Name:string; Price:int };;
let p = { Name="Test"; Price=42; };;
let p2 = { p with Name="Test2" };;

F# 创建关键字“with”,因为记录类型默认不可变。

现在,是否可以在 C# 中定义类似的扩展?似乎有点棘手,因为在 C# 中我不确定如何转换字符串

Name="Test2"

代表或表达?

4

4 回答 4

4
public static T With<T, U>(this T obj, Expression<Func<T, U>> property, U value)
    where T : ICloneable {
    if (obj == null)
        throw new ArgumentNullException("obj");
    if (property == null)
        throw new ArgumentNullException("property");
    var memExpr = property.Body as MemberExpression;
    if (memExpr == null || !(memExpr.Member is PropertyInfo))
        throw new ArgumentException("Must refer to a property", "property");
    var copy = (T)obj.Clone();
    var propInfo = (PropertyInfo)memExpr.Member;
    propInfo.SetValue(copy, value, null);
    return copy;
}

public class Foo : ICloneable {
    public int Id { get; set; } 
    public string Bar { get; set; }
    object ICloneable.Clone() {
        return new Foo { Id = this.Id, Bar = this.Bar };
    }
}

public static void Test() {
    var foo = new Foo { Id = 1, Bar = "blah" };
    var newFoo = foo.With(x => x.Bar, "boo-ya");
    Console.WriteLine(newFoo.Bar); //boo-ya
}

或者,使用复制构造函数:

public class Foo {
    public Foo(Foo other) {
        this.Id = other.Id;
        this.Bar = other.Bar;
    }
    public Foo() { }
    public int Id { get; set; } 
    public string Bar { get; set; }
}

public static void Test() {
    var foo = new Foo { Id = 1, Bar = "blah" };
    var newFoo = new Foo(foo) { Bar = "boo-ya" };
    Console.WriteLine(newFoo.Bar);
}

对 George 的优秀建议稍作改动,允许进行多项分配:

public static T With<T>(this T obj, params Action<T>[] assignments)
    where T : ICloneable {
    if (obj == null)
        throw new ArgumentNullException("obj");
    if (assignments == null)
        throw new ArgumentNullException("assignments");
    var copy = (T)obj.Clone();
    foreach (var a in assignments) {
        a(copy);
    }
    return copy;
}

public static void Test() {
    var foo = new Foo { Id = 1, Bar = "blah" };
    var newFoo = foo.With(x => x.Id = 2, x => x.Bar = "boo-ya");
    Console.WriteLine(newFoo.Bar);
}

我可能会使用第二个,因为(1)任何通用解决方案都会不必要地缓慢和复杂;(2) 它具有最接近您想要的语法(并且语法符合您的期望);(3) F# 复制和更新表达式的实现方式类似。

于 2011-07-26T16:03:56.880 回答
2

也许是这样的:

void Main()
{
    var NewProduct = ExistingProduct.With(P => P.Name = "Test2");
}

// Define other methods and classes here

public static class Extensions
{
    public T With<T>(this T Instance, Action<T> Act) where T : ICloneable
    {
        var Result = Instance.Clone();
        Act(Result);

        return Result;
    }
}
于 2011-07-26T16:00:15.980 回答
0

作为 lambda 函数的替代方法,您可以使用具有默认值的参数。唯一的小问题是您必须选择一些默认值,这意味着不要更改此参数(对于引用类型),但null应该是一个安全的选择:

class Product {
   public string Name { get; private set; }
   public int Price { get; private set; }
   public Product(string name, int price) {
     Name = name; Price = price;
   }

   // Creates a new product using the current values and changing
   // the values of the specified arguments to a new value
   public Product With(string name = null, int? price = null) {
     return new Product(name ?? Name, price ?? Price);
   }
 }

 // Then you can write:
 var prod2 = prod1.With(name = "New product");

您必须自己定义方法,但总是如此(除非您要使用反射,效率较低)。我认为语法也相当不错。如果你想让它和 F# 一样好,那么你必须使用 F# :-)

于 2011-07-26T16:24:47.373 回答
0

在 C# 中,除了扩展方法之外,没有本机功能可以做到这一点,但代价是什么?ab是引用类型,任何关于b基于(“with”)a 的建议都会立即引起我们正在使用多少个对象的混淆。只有一个吗?ba的副本吗?b指向a吗?

C# 不是 F#。

请参阅 Eric Lippert 回答的我之前的 SO 问题:

“我编写清晰代码的经验法则是:将所有副作用放在语句中;非语句表达式应该没有副作用。

更流畅的 C# / .NET

于 2011-07-26T16:37:19.673 回答