C #: An object having two constructors: how to restrict which properties are set together?

Say you have a Price object that accepts either (int quantity, decimal price), or a string containing "4 / 3.99". Is there a way to limit which properties can be set together? Feel free to correct me in my logic below.

Test: A and B are equal to each other, but Example C should not be allowed. Thus, the question How to ensure that all three parameters are not called, as in Example C?

AdPrice A = new AdPrice { priceText = "4/$3.99"}; // Valid AdPrice B = new AdPrice { qty = 4, price = 3.99m}; // Valid AdPrice C = new AdPrice { qty = 4, priceText = "2/$1.99", price = 3.99m};// Not 

Grade:

 public class AdPrice { private int _qty; private decimal _price; private string _priceText; 

Constructors:

  public AdPrice () : this( qty: 0, price: 0.0m) {} // Default Constructor public AdPrice (int qty = 0, decimal price = 0.0m) { // Numbers only this.qty = qty; this.price = price; } public AdPrice (string priceText = "0/$0.00") { // String only this.priceText = priceText; } 

Methods:

  private void SetPriceValues() { var matches = Regex.Match(_priceText, @"^\s?((?<qty>\d+)\s?/)?\s?[$]?\s?(?<price>[0-9]?\.?[0-9]?[0-9]?)"); if( matches.Success) { if (!Decimal.TryParse(matches.Groups["price"].Value, out this._price)) this._price = 0.0m; if (!Int32.TryParse(matches.Groups["qty"].Value, out this._qty)) this._qty = (this._price > 0 ? 1 : 0); else if (this._price > 0 && this._qty == 0) this._qty = 1; } } private void SetPriceString() { this._priceText = (this._qty > 1 ? this._qty.ToString() + '/' : "") + String.Format("{0:C}",this.price); } 

Accessors:

  public int qty { get { return this._qty; } set { this._qty = value; this.SetPriceString(); } } public decimal price { get { return this._price; } set { this._price = value; this.SetPriceString(); } } public string priceText { get { return this._priceText; } set { this._priceText = value; this.SetPriceValues(); } } } 
+4
source share
5 answers

If you're just going to assign values ​​directly to properties, as you do and you have a default constructor, why bother with constructors at all?

If the priceText and price / qty tags must be mutable externally, then under what conditions do you think that the initialization of the object is completed: -

 AdPrice C = new AdPrice(); C.qty = 4; C.priceText = "2/$1.99"; C.price = 3.99m 

The above code is the same as you, without syntactic sugar. To prevent your example from happening, you will need to prevent this.

My suggestion was to make the priceText property private. To initialize an object using a string, you will need to use the appropriate constructor.

+2
source

Hmmmm .... instead of fighting with the compiler, maybe you just need to rethink your API. Have you considered the following:

  • No settings. Your class must be immutable so that it is fully initialized through the constructor and cannot be initialized in an invalid state.

  • If you insist on customizers, then your PriceText property can only be in read mode, while others are read / written. At the very least, you do not need to check the text passed to this property.

  • It is possible to completely remove the PriceText property, override the string representation of your object in the .ToString method.

The last option is the best approach, in my opinion. I don’t think that users should pass in pseudo-serialized strings to your class, because this requires parsing and validation - and the burden should really be on the client using your class than your class.

+11
source

How to make setters in properties private and add methods to change prices, or even without methods, so that prices are set only when creating a new object.

+5
source

For part of the string, it is useful to have a static method that parses the string and returns an instance of Price .

eg.

 Price newPrice = Price.FromString("4/$3.99"); Console.WriteLine("{0} qty for {1}", newPrice.Quantity, newPrice.Price); 

Here FromString is a static method.
This is the same as Enum.Parse if you want to see an example.

+5
source

I propose making private property setters private and just providing two constructors - one accepts quantity and price, and the other a textual representation. In my opinion, your price type has value type semantics and therefore should be unchanged.

Further, I believe that object initializers are heavily abused. After calling the constructor, the object must be fully initialized and in a consistent state that satisfies all invariants. You can achieve this by providing a well-designed set of constructors, but you usually cannot ensure it by providing a default constructor and relying on the use of object initializers. The consumer can simply call the default constructor and do nothing.

 var employee = new Employee { Id = 42, FirstName = "John", LastName = "Doe" }; 

In my opinion, this is a really bad design. What is the semantics of the following code?

 var employee = new Employee(); 

It is just an object without any attributes. Useless. I believe that it would be much better to ensure that the employee has at least an identifier without providing a default constructor. If each employee instance requires a value for other properties, it depends on the actual context, but if they have these properties, they should, of course, become constructor arguments.

 var employee = new Employee(42); 
+2
source

Source: https://habr.com/ru/post/1311304/


All Articles