関数型C#:不変性

これは、機能的なC#プログラミングに関する小規模シリーズの最初の記事です。 このシリーズは、皆さんが思うようにLINQについてではなく、より基本的なことについてです。 F #thに触発されました。







不変性



エンタープライズ開発の世界における最大の問題は、複雑性との戦いです。 コードの読みやすさは、多かれ少なかれ複雑なプロジェクトを書くときに最初に達成すべきことです。 これがないと、コードを理解し、これに基づいてインテリジェントな意思決定を行う能力が著しく損なわれます。



可変オブジェクトはコードを読むときに役立ちますか? 例を見てみましょう:



// Create search criteria var queryObject = new QueryObject<Customer>(name, page: 0, pageSize: 10); // Search customers IReadOnlyCollection<Customer> customers = Search(queryObject); // Adjust criteria if nothing found if (customers.Count == 0) AdjustSearchCriteria(queryObject, name); // Is queryObject changed here? Search(queryObject);
      
      





queryObjectは、カスタムを再度検索するまでに変更されましたか? たぶんはい。 またはそうでないかもしれません。 このオブジェクトがAdjustSearchCriteriaメソッドを使用して変更されているかどうかによります。 調べるには、このメソッドの内部を調べる必要があります。そのシグネチャは十分な情報を提供しません。



これを次のコードと比較してください。



 // Create search criteria var queryObject = new QueryObject<Customer>(name, page: 0, pageSize: 10); // Search customers IReadOnlyCollection<Customer> customers = Search(queryObject); if (customers.Count == 0) { // Adjust criteria if nothing found QueryObject<Customer> newQueryObject = AdjustSearchCriteria(queryObject, name); Search(newQueryObject); }
      
      





この例では、AdjustSearchCriteriaが新しい基準オブジェクトを作成し、後で新しい検索に使用することは明らかです。



可変データ構造の問題は何ですか?





不変型を作成する方法



C#の将来のバージョンでは、不変キーワードが表示される可能性があります。 その助けを借りて、単にその署名を見ることによって、型が不変かどうかを理解することが可能になります。 それまでは、持っているものを使用する必要があります。



比較的単純なクラスがある場合は、不変にすることを検討してください。 このガイドラインは、 値オブジェクトの概念と相関しています。



たとえば、販売されている多数の製品を記述するProductPileクラスを取り上げます。



 public class ProductPile { public string ProductName { get; set; } public int Amount { get; set; } public decimal Price { get; set; } }
      
      





不変にするには、そのプロパティを読み取り専用としてマークし、コンストラクターを追加します。



 public class ProductPile { public string ProductName { get; private set; } public int Amount { get; private set; } public decimal Price { get; private set; } public ProductPile(string productName, int amount, decimal price) { Contracts.Require(!string.IsNullOrWhiteSpace(productName)); Contracts.Require(amount >= 0); Contracts.Require(price > 0); ProductName = productName; Amount = amount; Price = price; } }
      
      





ここで、製品の1つを販売するたびにAmountプロパティを1つずつ減らす必要があるとします。 既存のオブジェクトを変更する代わりに、既存のオブジェクトに基づいて新しいオブジェクトを作成できます。



 public class ProductPile { public string ProductName { get; private set; } public int Amount { get; private set; } public decimal Price { get; private set; } public ProductPile(string productName, int amount, decimal price) { Contracts.Require(!string.IsNullOrWhiteSpace(productName)); Contracts.Require(amount >= 0); Contracts.Require(price > 0); ProductName = productName; Amount = amount; Price = price; } public ProductPile SubtractOne() { return new ProductPile(ProductName, Amount – 1, Price); } }
      
      





これにより何が得られますか?





制限事項



もちろん、すべての良い習慣には代償があります。 小さなクラスは不変性を最大限に活用しますが、このアプローチは大きな型の場合に常に適用できるとは限りません。



まず、不変性には潜在的なパフォーマンスの問題が伴います。 オブジェクトが非常に大きい場合は、変更ごとにコピーを作成する必要があります。



良い例は、 不変のコレクションです。 作成者は、潜在的なパフォーマンスの問題を考慮し、コレクションの状態を変更できる特別なBuilderクラスを追加しました。 コレクションが必要な状態になった後、変更不可能に変換することでコレクションをファイナライズできます。



 var builder = ImmutableList.CreateBuilder<string>(); builder.Add(“1”); // Adds item to the existing object ImmutableList<string> list = builder.ToImmutable(); ImmutableList<string> list2 = list.Add(“2”); // Creates a new object with 2 items
      
      





おわりに



ほとんどの場合、不変型(特にかなり単純な場合)はコードを改善します。



シリーズの他の記事:





記事の英語版: Functional C#:不変性



All Articles