yeomandev
yeomandev

Reputation: 11796

How to prevent property setter from modifying private property data

Let me explain my question by posing a hypothetical situation. Lets start with a class:

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

And then 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 has made the property MyPaymentDetails read-only.

So I cannot instantiate PaymentHelper and modify MyPaymentDetails like this:

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

But I can modify the public properties inside of ph.MyPaymentDetails like this:

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

How do I prevent that from working? Or is there no good way of doing that?

Upvotes: 6

Views: 8433

Answers (8)

JamieSee
JamieSee

Reputation: 13020

So there are two ways that I can think of to deal with this. One is really 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.");
             }
         }
     }

The simple solution only allows values to be set once. Changing anything requires creating a new instance of the class.

The other is 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
    }
}

The complex solution allows a changeable backing store, but presents a readonly version to the consumer that cannot be changed by outsiders to your helper class.

Note that both patterns only work if you implement them all the way down the object graph or stick to value types and strings.

Upvotes: 1

3Dave
3Dave

Reputation: 29051

Edit: You could always let the behavior of value types take care of this for you. Change PaymentDetails to a struct instead of a 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 to

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

You'll get a compiler error telling you that you can't do this. Since value types are returned, well, by value, you can't modify the .Status property.

Or...

If PaymentDetails and PaymentHelper are declared in the same class library (separate from the code you want to prevent from writing to the .MyPaymentDetails property, you could 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 anything declared outside of that class library from writing to .Id or .Status.

Or, force access to .Id and .Status to go through the helper class instead of allowing read access to a .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're going to do that, you could just

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

public class PaymentHelper : PaymentDetails
{
}

... assuming that this sort of inheritance fits with the rest of your architecture.

Or, just to illustrate the interface suggestion proposed 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; } }
}

Upvotes: 1

brgerner
brgerner

Reputation: 4371

Yet another solution: Delegate from PaymentHelper to PaymentDetails.
This is to add the same properties to PaymentHelper as in PaymentDetails.

If you have many properties you can let generate the delegating properties into PaymentHelper by ReSharper. Place cursor on *_paymentDetails* of line

private PaymentDetails _paymentDetails;

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

Upvotes: 0

brgerner
brgerner

Reputation: 4371

Yet another solution: Make setters internal
This is the pragmatical way if the PaymentHelper is in the same assembly of PaymentDetails and the clients of PaymentHelper are in another assembly.

Upvotes: 0

Adam Houldsworth
Adam Houldsworth

Reputation: 64487

The idea of protecting the properties of a complex type that is itself a property isn't available from a language construct at that level.

One option is to design the contained type in such a way as to make its properties read-only using the access modifiers (public set, protected set, private set, etc).

My preference is to expose it as an interface to 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; }
}

Code inside the PaymentHelper class can then use the PaymentDetails class directly, and code outside the class won't be able to use PaymentDetails unless they cast directly to it, which you can stop if you don't release the PaymentDetails class and only provide the interface.

Of course, you can never really stop the determined person who may use reflection to set things. I tend to let these people break the code :-)

Upvotes: 5

Ben Hoffstein
Ben Hoffstein

Reputation: 103345

Another solution is not to expose the PaymentDetails object directly, but rather wrap the properties you wish to expose. For example:

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
    }
}

Upvotes: 1

Femaref
Femaref

Reputation: 61437

You can't prevent that, the property returns a refrence to a PaymentDetails, and once somebody has that, it is out of your control.

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

You can also assign access modifiers for the PaymentDetails class like so:

public string Status { get; private set; }

if you don't need the class elsewhere with a public setter.

Upvotes: 0

Grant Thomas
Grant Thomas

Reputation: 45083

A property may apply access modifiers to individual accessors, for instance:

public string Status { get; private set; }

The scope of access is left to your circumstance. Keeping it private, I'm sure you can tell, will mean only elements within the scope of the current class can use the setter, protected would allow inheritors to use it, etc.

Obviously your classes need to be engineered properly from the bottom up, so as to account for appropriate scoping and robust management when used further up the hierarchy.

Upvotes: 8

Related Questions