How to prevent a property setting tool from changing private property data

Let me explain my question by setting a hypothetical situation. Let's start with the class:

public class PaymentDetails { public int Id {get;set;} public string Status {get;set;} } 

And I have another class:

 public class PaymentHelper { private PaymentDetails _paymentDetails; public PaymentDetails MyPaymentDetails{ get { return _paymentDetails; } } public PaymentHelper() { _paymentDetails = new PaymentDetails(); } public void ModifyPaymentDetails(string someString) { // code to take the arguments and modify this._paymentDetails } } 

OK, so I have these two classes. PaymentHelper made the property MyPaymentDetails read-only.

Therefore, I cannot create an instance of PaymentHelper and change MyPaymentDetails as follows:

 PaymentHelper ph = new PaymentHelper(); ph.MyPaymentDetails = new PaymentDetails(); // Not allowed!!! 

But I can change the public properties inside ph.MyPaymentDetails as follows:

 ph.MyPaymentDetails.Status = "Some status"; // This is allowed 

How can I prevent this? Or is there no good way to do this?

+4
source share
8 answers

The idea of ​​protecting properties of a complex type, which in itself is a property, is not available from the language structure at this level.

One option is to create the contained type so that its properties are read-only using access modifiers (public set, protected set, private set, etc.).

My preference is to distribute it as an interface for public consumers:

 public class PaymentHelper { private PaymentDetails _paymentDetails; public IPaymentDetails MyPaymentDetails{ get { return _paymentDetails; } } public PaymentHelper() { _paymentDetails = new PaymentDetails(); } public void ModifyPaymentDetails(string someString) { // code to take the arguments and modify this._paymentDetails } } interface IPaymentDetails { int Status { get; } } 

The code inside the PaymentHelper class can then directly use the PaymentDetails class, and the code outside the class cannot use PaymentDetails unless they are directly passed to it, which you can stop, t free the PaymentDetails class and provide only an interface.

Of course, you can never stop a specific person who can use reflection to define things. I try to get these people to break the code :-)

+3
source

The property can apply access modifiers to individual accessories, for example:

 public string Status { get; private set; } 

The scope of access is up to you. Keeping it confidential, I'm sure you can say it will mean that only elements within the current class can use the installer, protected will allow the heirs to use it, etc.

Obviously, your classes must be correctly designed from the bottom up to take into account the appropriate definition of the scope and reliable management in the future use of the hierarchy.

+7
source

Another solution is not to expose the PaymentDetails object directly, but rather to wrap the properties you want to open. For instance:

 public class PaymentHelper { private PaymentDetails _paymentDetails; public string PaymentDetailsStatus { get { return _paymentDetails.Status; } } public PaymentHelper() { _paymentDetails = new PaymentDetails(); } public void ModifyPaymentDetails(string someString) { // code to take the arguments and modify this._paymentDetails } } 
+1
source

Edit: You can always let the behavior of value types take care of this for you. Change PaymentDetails for structure instead of class:

 public struct PaymentDetails { public int Id { get; set; } public string Status { get; set; } } public class PaymentHelper { public PaymentDetails Details { get; set; } } 

If you then try

  ph.Details.Status = "Some status"; // 

You will get a compiler error telling you that you cannot do this. Since the return values ​​of value types are, well, by value, you cannot change the .Status property.

Or...

If PaymentDetails and PaymentHelper declared in the same class library (separately from the code that you want to prevent writing to the .MyPaymentDetails property, you can use:

 public class PaymentDetails { public int Id { get; internal set; } public string Status { get; internal set; } } public class PaymentHelper { public PaymentDetails Details { get; private set; } } 

which will prevent any message declared outside this class library from being .Id to .Id or .Status .

Or, launch access to .Id and .Status to go through the helper class, instead of allowing access to access the .Details property:

 public class PaymentHelper { private PaymentDetails _details; public string Id { get { return _details.Id; } private set { _details.Id=value; } } public string Status { get { return _details.Status; } private set { _details.Status = value; } } } 

Of course, if you are going to do this, you can simply

 public calss PaymentDetails { public int Id { get; protected set; } public string Status { get; protected set; } } public class PaymentHelper : PaymentDetails { } 

... assuming that this type of inheritance matches the rest of your architecture.

Or just to illustrate the interface proposal suggested by @MrDisappointment

 public interface IDetails { int Id { get; } string Status { get; } } public class PaymentDetails : IDetails { public int Id { get; private set; } public string Status { get; private set; } } public class PaymentHelper { private PaymentDetails _details; public IDetails Details { get { return _details; } private set { _details = value; } } } 
+1
source

So, there are two ways that I can think of to deal with this. One of them is very simple:

 public class PaymentDetails { private int _id; private bool _idSet = false; int Id { get { return _id; } set { if (_idSet == false) { _id = value; _idSet == true; } else { throw new ArgumentException("Cannot change an already set value."); } } } private string _status; private bool _statusSet = false; string Status { get { return _status; } set { if (_statusSet == false) { _status = value; _statusSet = true; } else { throw new ArgumentException("Cannot change an already set value."); } } } 

A simple solution allows you to set values ​​only once. Changing something requires creating a new instance of the class.

Another rather complex, but very versatile:

 public interface IPaymentDetails : IEquatable<IPaymentDetails> { int Id { get; } string Status { get; } } public class PaymentDetails : IPaymentDetails, IEquatable<IPaymentDetails> { public PaymentDetails() { } public PaymentDetails(IPaymentDetails paymentDetails) { Id = paymentDetails.Id; Status = paymentDetails.Status; } public static implicit operator PaymentDetails(PaymentDetailsRO paymentDetailsRO) { PaymentDetails paymentDetails = new PaymentDetails(paymentDetailsRO); return paymentDetails; } public override int GetHashCode() { return Id.GetHashCode() ^ Status.GetHashCode(); } public bool Equals(IPaymentDetails other) { if (other == null) { return false; } if (this.Id == other.Id && this.Status == other.Status) { return true; } else { return false; } } public override bool Equals(Object obj) { if (obj == null) { return base.Equals(obj); } IPaymentDetails iPaymentDetailsobj = obj as IPaymentDetails; if (iPaymentDetailsobj == null) { return false; } else { return Equals(iPaymentDetailsobj); } } public static bool operator == (PaymentDetails paymentDetails1, PaymentDetails paymentDetails2) { if ((object)paymentDetails1 == null || ((object)paymentDetails2) == null) { return Object.Equals(paymentDetails1, paymentDetails2); } return paymentDetails1.Equals(paymentDetails2); } public static bool operator != (PaymentDetails paymentDetails1, PaymentDetails paymentDetails2) { if (paymentDetails1 == null || paymentDetails2 == null) { return ! Object.Equals(paymentDetails1, paymentDetails2); } return ! (paymentDetails1.Equals(paymentDetails2)); } public int Id { get; set; } public string Status { get; set; } } public class PaymentDetailsRO : IPaymentDetails, IEquatable<IPaymentDetails> { public PaymentDetailsRO() { } public PaymentDetailsRO(IPaymentDetails paymentDetails) { Id = paymentDetails.Id; Status = paymentDetails.Status; } public static implicit operator PaymentDetailsRO(PaymentDetails paymentDetails) { PaymentDetailsRO paymentDetailsRO = new PaymentDetailsRO(paymentDetails); return paymentDetailsRO; } public override int GetHashCode() { return Id.GetHashCode() ^ Status.GetHashCode(); } public bool Equals(IPaymentDetails other) { if (other == null) { return false; } if (this.Id == other.Id && this.Status == other.Status) { return true; } else { return false; } } public override bool Equals(Object obj) { if (obj == null) { return base.Equals(obj); } IPaymentDetails iPaymentDetailsobj = obj as IPaymentDetails; if (iPaymentDetailsobj == null) { return false; } else { return Equals(iPaymentDetailsobj); } } public static bool operator == (PaymentDetailsRO paymentDetailsRO1, PaymentDetailsRO paymentDetailsRO2) { if ((object)paymentDetailsRO1 == null || ((object)paymentDetailsRO2) == null) { return Object.Equals(paymentDetailsRO1, paymentDetailsRO2); } return paymentDetailsRO1.Equals(paymentDetailsRO2); } public static bool operator != (PaymentDetailsRO paymentDetailsRO1, PaymentDetailsRO paymentDetailsRO2) { if (paymentDetailsRO1 == null || paymentDetailsRO2 == null) { return ! Object.Equals(paymentDetailsRO1, paymentDetailsRO2); } return ! (paymentDetailsRO1.Equals(paymentDetailsRO2)); } public int Id { get; private set; } public string Status { get; private set;} } public class PaymentHelper { private PaymentDetails _paymentDetails; public PaymentDetailsRO MyPaymentDetails { get { return _paymentDetails; } } public PaymentHelper() { _paymentDetails = new PaymentDetails(); } public void ModifyPaymentDetails(string someString) { // code to take the arguments and modify this._paymentDetails } } 

A comprehensive solution allows you to change the backup storage, but it is a read-only version for the consumer, which cannot be changed by outsiders in your helper class.

Please note that both templates only work if you implement them until the end of the object graph or adhere to types and value strings.

+1
source

You cannot prevent this, the property returns refrence to PaymentDetails , and as soon as someone receives it, it cannot be controlled.

However, you can just wrap PaymentDetails . Instead of returning it verbatim, we offer only getters for our public properties.

You can also assign access modifiers to the PaymentDetails class as follows:

 public string Status { get; private set; } 

unless you need a class elsewhere with a public network device.

0
source

Another solution: Make setters internal
This is pragmatic if PaymentGelper is in the same PaymentDetails assembly and PaymentHelper clients are in a different assembly.

0
source

Another solution: a delegate from PaymentHelper to PaymentDetails.
This should add the same properties in PaymentHelper as in PaymentDetails.

If you have many properties, you can enable the generation of delegation properties in PaymentHelper using ReSharper. Place the cursor on the * _paymentDetails * line

 private PaymentDetails _paymentDetails; 

Press Alt + Insert-> Delegating Members. Then all PaymentHelper properties are delegated to PaymentDetails properties.

0
source

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


All Articles