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

发布于 2024-11-26 19:18:03 字数 324 浏览 6 评论 0原文

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"

为委托或表达式?

F# has a convenient feature "with", example:

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.

Now, is it possible to define a similar extension in C#?
seems it's a bit tricky, as in C# i'm not sure how to convert a string

Name="Test2"

to a delegate or expression?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

何以畏孤独 2024-12-03 19:18:03
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);
}

乔治的出色建议略有不同,它允许多次赋值:

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# 复制和更新表达式的实现类似。

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; (2) it has the closest syntax to what you want (and the syntax does what you expect); (3) F# copy-and-update expressions are implemented similarly.

ぃ弥猫深巷。 2024-12-03 19:18:03

也许是这样的:

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

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;
    }
}
翻了热茶 2024-12-03 19:18:03

作为 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# :-)

As an alternative to lambda function, you can use parameters with default values. 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:

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# :-)

橙味迷妹 2024-12-03 19:18:03

如果没有扩展方法,C# 中没有原生能力可以做到这一点,但代价是什么? ab 是引用类型以及 b< /strong> 基于(“with”)a,这会导致我们立即混淆我们正在处理的对象数量。只有一个吗? ba 的副本吗? b 是否指向 a

C# 不是 F#。

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

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

更流畅的 C# / .NET

There is no native ability to do this in C# short of an extension method, but at what cost? 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. Is there only one? Is b a copy of a ? Does b point to a ?

C# is not F#.

Please see a previous SO question of mine as answered by Eric Lippert:

"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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文