简体   繁体   English

C#:如何在F#中将扩展方法定义为“with”?

[英]C#: how to define an extension method as “with” in F#?

F# has a convenient feature "with", example: F#有一个方便的功能“with”,例如:

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

F# created keyword "with" as the record types are by default immutable. F#创建关键字“with”作为记录类型默认是不可变的。

Now, is it possible to define a similar extension in C#? 现在,是否可以在C#中定义类似的扩展? seems it's a bit tricky, as in C# i'm not sure how to convert a string 看起来有点棘手,就像在C#中我不确定如何转换字符串

Name="Test2"

to a delegate or expression? 代表或表达?

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
}

Or, using a copy constructor: 或者,使用复制构造函数:

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);
}

And a slight variation on George's excellent suggestion, that allows for multiple assignments: 乔治的出色建议略有不同,允许多项任务:

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);
}

I would probably use the second one since (1) any general purpose solution is going to be unnecessarily slow and convoluted; 我可能会使用第二个,因为(1)任何通用解决方案将会不必要地缓慢和复杂化; (2) it has the closest syntax to what you want (and the syntax does what you expect); (2)它具有与您想要的语法最接近的语法(并且语法符合您的期望); (3) F# copy-and-update expressions are implemented similarly. (3)F#复制和更新表达式的实现类似。

Maybe something like this: 也许是这样的:

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;
    }
}

As an alternative to lambda function, you can use parameters with default values. 作为lambda函数的替代方法,您可以使用具有默认值的参数。 The only minor issue is that you have to pick some default value that means do not change this parameter (for reference types), but null should be a safe choice: 唯一的小问题是你必须选择一些默认值,这意味着不要更改此参数 (对于引用类型),但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");

You have to define the method yourself, but that's always the case (unless you're going to use reflection, which less efficient). 你必须自己定义方法,但情况总是如此(除非你要使用效率较低的反射)。 I think the syntax is reasonably nice too. 我认为语法也相当不错。 If you want to make it as nice as in F#, then you'll have to use F# :-) 如果你想让它像F#一样好,那么你将不得不使用F#:-)

There is no native ability to do this in C# short of an extension method, but at what cost? 在C#中没有原生能力来做这个扩展方法,但成本是多少? a and b are reference types and any suggestion that b is based ("with") on a causes immediate confusion as to how many objects we are working with. ab一个原因,立即混乱引用类型和任何建议,b为基础(“有”),作为对我们有多少对象一起工作。 Is there only one? 只有一个吗? Is b a copy of a ? B 副本? Does b point to a ? b指向一个

C# is not F#. C#不是F#。

Please see a previous SO question of mine as answered by Eric Lippert: 请看Eric Lippert回答的我之前的SO问题:

"Amongst my rules of thumb for writing clear code is: put all side effects in statements; non-statement expressions should have no side effects. " “编写清晰代码的经验法则之一是: 将所有副作用放在语句中;非语句表达式应该没有副作用。

More fluent C# / .NET 更流利的C#/ .NET

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM