C#:F#で拡張メソッドを「with」として定義する方法は?
-
27-10-2019 - |
質問
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"
代表者や表現に?
解決
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#コピーアンドアップデート式が同様に実装されています。
他のヒント
多分このようなもの:
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;
}
}
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#:-)を使用する必要があります。
拡張法に匹敵するc#でこれを行うネイティブ能力はありませんが、どのようなコストですか? a と b 参照タイプとその提案です b 基になっています( "with") a 私たちが取り組んでいるオブジェクトの数について、すぐに混乱を引き起こします。 1つしかありませんか?は b のコピー a ?します b 指し示す a ?
C#はF#ではありません。
Eric Lippertが答えた私の以前の質問をご覧ください:
「明確なコードを書くための私の経験則の中には、次のものがあります。 すべての副作用をステートメントに入れます。非ステートメント式には副作用がないはずです。"