2011-07-26 58 views
8

F#有一個方便的功能「與」,例如:C#:如何在F#中將擴展方法定義爲「with」?

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

F#創建關鍵字「與」作爲記錄類型是默認不可改變的。

現在,是否有可能在C#中定義一個類似的擴展? 似乎這是一個有點棘手,因爲在C#我不知道如何將字符串

Name="Test2" 

爲委託或表達式轉換?

+0

我希望看到更多的這些問題作爲實用語言成爲主流。常見的副作用是「我怎樣才能[我最喜歡的語言] [某些功能特性]?」 – Daniel

回答

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

以及喬治的指教略有變化,即允許多個任務:

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#複製和更新表達式類似地實現。

+0

是否可以這樣做:var newFoo = foo.With(Bar =「boo-ya」); ? – athos

+0

@athos:是的,我添加了另一個(更好,雖然不是通用)方法。 – Daniel

+0

@athos:我希望有一個拷貝構造函數約束(也許'where T:new(T)'),所以我們可以在通用解決方案上反思。也許作爲不變性在C#中獲得更好的支持,我們將看到使這更容易的功能。 – Daniel

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

是否可以這樣做:var NewProduct = ExistingProduct.With(Name =「Test2」); ? – athos

+0

這是不可能的,你可以,使用String.Split和Reflection,做類似於: var NewProduct = ExistingProduct.With(「Name ='Test2'」); 但是你沒有編譯時間檢查,並且你獲得了顯着的性能提升,所以我不會推薦它。拉姆達表達式大概和你可能得到的一樣好。 – ForbesLindesay

+0

非常可讀。 +1 – vlad

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

+1

我同意,只是使用F#! – Daniel

+0

哈哈托馬斯,你知道嗎,我現在正在閱讀你的「F#語言評論」!很高興在這裏看到你:) – athos

+0

雖然這個「With」取決於產品類型,但使用帶有默認值的參數是一個聰明棘手和有趣的事情! :) – athos

0

在C#中沒有原生的能力來做這個擴展方法,但代價是什麼? 一個b是引用類型和一個立即引起混淆任何暗示b是根據(「有」),作爲對我們有多少對象一起工作。只有一個?是b副本ab指向a

C#不是F#。

請參閱我的前一個SO問題的回答埃裏克利珀:

「當中寫清楚代碼我的經驗法則是:把所有的副作用報告;非語句表達式應該有沒有副作用影響。

More fluent C#/.NET

+0

嗨Andleer,埃裏克的觀點總是值得第二個想法。只是..如果我們檢查當前的TDD框架,比如NUnit,有類似於你的問題和我的線的行......我認爲「編寫與自然英語一樣接近的代碼」在那裏有一些優點......如果你認爲否則,好吧,我們仍然可以將這個問題作爲一些測驗的樂趣! :p – athos

+0

阿索斯,在個人層面上,我對此沒有強烈的感受。在專業層面上,我喜歡埃裏克的回答,因爲它允許我與我的同事進行強烈的爭論。沒有必要繼續進行此操作。祝你好運! – andleer

+0

@andleer - 我認爲在C#中如何獲得類似於F#的'with'的構造是一個公平的問題。如果使用_immutable_類型,使用它只是有意義的,但這在C#中是非常有用的方法(因爲所有值類型都應該是不可變的)。如果這種類型是不可變的,那麼你所有的擔憂都不是問題。當然,使用可變類型時,事情會變得更加困難,但當我在問題中看到「F#record」時,我不會這麼想:-)。 –