如何防止属性设置器修改私有属性数据

时间:2012-02-09 15:05:10

标签: c# oop

让我通过提出一个假设的情况来解释我的问题。让我们从课程开始:

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

然后我又开了一个班:

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

好的,所以我有这两个课程。 PaymentHelper已将属性MyPaymentDetails设为只读。

所以我不能像这样实例化PaymentHelper并修改MyPaymentDetails

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

但我可以像这样修改ph.MyPaymentDetails内的公共属性:

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

如何防止这种情况发生?或者没有好办法吗?

8 个答案:

答案 0 :(得分:7)

属性可以将访问修饰符应用于各个访问者,例如:

public string Status { get; private set; }

访问范围取决于您的情况。保持私密性,我相信你可以说,只意味着当前类范围内的元素只能使用setter,protected会允许继承者使用它等等。

显然,您的类需要从下到上正确设计,以便在层次结构中进一步使用时考虑适当的范围和强大的管理。

答案 1 :(得分:4)

保护本身属性的复杂类型的属性的想法不能从该级别的语言构造中获得。

一种选择是设计包含的类型,以便使用访问修饰符(公共集,受保护集,私有集等)使其属性成为只读。

我的偏好是将其作为接口公开给公众消费者:

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

PaymentHelper类中的代码可以直接使用PaymentDetails类,类外的代码将无法使用PaymentDetails,除非它们直接转换为它,你如果您不释放PaymentDetails类并仅提供界面,则可以停止。

当然,你永远不能真正阻止那些可能会使用反思来设定事物的人。我倾向于让这些人破坏代码: - )

答案 2 :(得分:1)

另一种解决方案不是直接公开PaymentDetails对象,而是包装您希望公开的属性。例如:

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

答案 3 :(得分:1)

编辑:您可以随时让值类型的行为为您解决此问题。将PaymentDetails更改为结构而不是类:

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

public class PaymentHelper
{
  public PaymentDetails Details { get; set; } 
}

如果您尝试

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

您将收到编译器错误,告诉您无法执行此操作。由于值类型是按值返回的,因此您无法修改.Status属性。

或者...

如果PaymentDetailsPaymentHelper在同一个类库中声明(与您希望阻止写入.MyPaymentDetails属性的代码分开,则可以使用:

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

public class PaymentHelper
{
    public PaymentDetails Details { get; private set; } 
}

会阻止在该类库外声明的任何内容写入.Id.Status

或者,强制访问.Id.Status以通过帮助程序类,而不是允许对.Details属性的读取权限:

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

当然,如果你打算这样做,你可以

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

public class PaymentHelper : PaymentDetails
{
}

...假设这种继承适合您的其他架构。

或者,只是为了说明@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; } }
}

答案 4 :(得分:1)

因此,有两种方法可以解决这个问题。一个很简单:

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.");
             }
         }
     }

简单的解决方案只允许设置一次值。更改任何内容都需要创建该类的新实例。

另一个相当复杂但功能多样:

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

复杂的解决方案允许更改后备存储,但向消费者提供一个只读版本,外人无法将其更改为帮助程序类。

请注意,只有在对象图表中一直实现它们或坚持使用值类型和字符串时,这两种模式才有效。

答案 5 :(得分:0)

你不能阻止这种情况,该属性会向PaymentDetails返回一个参考,一旦有人拥有它,它就会超出你的控制范围。

但是,您可以打包PaymentDetails。而不是逐字归还,只为其公共财产提供吸气剂。

您还可以为PaymentDetails类分配访问修饰符,如下所示:

public string Status { get; private set; }

如果您不需要使用公共设置器的其他类。

答案 6 :(得分:0)

另一个解决方案:让内部人员成为内部人士 如果PaymentHelper在PaymentDetails的同一个程序集中,且PaymentHelper的客户端在另一个程序集中,那么这是实用的方法。

答案 7 :(得分:0)

另一个解决方案:从 PaymentHelper 委派给 PaymentDetails
这是为 PaymentDelper 添加与 PaymentDetper 相同的属性。

如果您有许多属性,可以让ReSharper将委派属性生成到 PaymentHelper 中。将光标放在行

的* _paymentDetails *上
private PaymentDetails _paymentDetails;

按Alt + Insert-&gt;委派会员。然后,所有 PaymentHelper 属性都委托给 PaymentDetails 属性。