2011-07-26 13 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番目を使用します。 (2)それはあなたが望むものに最も近い構文を持ちます(そして構文はあなたが期待することを行います)。 (3)F#のコピーアンドアップデート式も同様に実装されています。

+0

それは可能です:var newFoo = foo.With(Bar = "boo-ya"); ? – athos

+0

@athos:はい、私は別の(より良い、汎用ではありませんが)メソッドを追加しました。 – Daniel

+0

@athos:コピーコンストラクタの制約がありました(たぶん '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

ラムダ関数の代わりに、デフォルト値でパラメータを使用することができます。唯一のマイナーな問題は、が(参照型のため)このパラメータを変更しないことを意味いくつかのデフォルト値を選択しなければならないことですが、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私たちが作業しているどのように多くのオブジェクトへとすぐに混乱を引き起こしに(「と」)をベースにしていることを任意の提案です。 1つしかありませんか? Bコピーですか?ありますかbを指す

C#はF#ではありません。

エリックリペットによって答えとして私の前のSOの質問を参照してください。

「明確なコードを書くための親指の私のルールの中で:は声明で、すべての副作用を入れ、非文の表現には側面を持つべきではありません効果。

More fluent C#/.NET

+0

こんにちはAndleer、エリックのポイントは常に第二の考えに値する。ちょうど..私たちがNUnitのような現在のTDDフレームワークをチェックすれば、質問と似たような行があります... "自然言語に近いコードを書いてください"と思うように、いくつかのメリットがあります。そうでなければ、私たちはまだこの質問を楽しいクイズとして取ることができます! :p – athos

+0

アトス、個人的なレベルで私はこれについて強く感じることはありません。プロのレベルでは、エリックの答えが好きだったので、私は同僚と強い議論をすることができました。これを回避する必要はありません。がんばろう! – andleer

+0

@andleer - C#でF#の 'with'と似たような構造をどうやって得るのかについて質問するのは公正な質問だと思います。これは、_immutable_型で作業している場合にのみ意味がありますが、これはC#での便利なアプローチです(すべての値の型は不変でなければなりません)。タイプが不変の場合、あなたの懸念事項はまったく問題ではありません。もちろん、変更可能な型を使用する場合、事態はより困難になりますが、それは質問で "F#record"が表示されたときに私が考えるものではありません:-)。 –

関連する問題