数据一致性
定义如下Address结构体:
public struct Address { private string province; private string city; private string zip; public string Province { get { return province; } set { province = value; } } public string City { get { return city; } set { city = value; } } public string Zip { get { return zip; } set { CheckZip(value); // 验证格式 zip = value; } } private void CheckZip(string value) { string pattern = @"\d{6}"; if (!Regex.IsMatch(value, pattern)) { throw new Exception("Zip is invalid!"); } } }
接下来使用刚刚创建的结构体
try { Address a = new Address(); a.Province = "陕西"; a.City = "西安"; a.Zip = "710068"; a.City = "青岛"; a.Zip = "12345"; a.Province = "山东"; } catch { }
结果出现了数据不一致的问题,当给Zip赋值的时候,因为发生了异常,所以出现了Province是陕西,City却是青岛这种情况。另外,在多线程下,也会出现类似的情况。那么如何改进呢?先来看一下类型可以具有的两个特性:原子性和常量性。
对象的原子性:对象的状态是一个整体,如果一个字段改变,其他字段也要同时作出相应改变。简单来说,就是要么不改,要改全改。
对象的常量性:对象的状态一旦确定,就不能再次更改了。如果想再次更改,需要重新构造一个对象。
对于原子性,实施的办法是添加一个构造函数,在这个构造函数中为对象的所有字段赋值。为了实施常量性,将属性中的set访问器删除,同时将字段声明为readonly。
上面的方法解决了数据不一致的问题,但还漏掉一点:当类型内部维护着一个引用类型的字段时,比如数组,尽管将它声明为readonly,在类型外部还是可以对它进行修改。现在我们修改Address 类,添加一个数组phones,存储电话号码:
private readonly string[] phones; public Address(string province, string city, string zip, string[] phones) { // 略... this.phones = phones; } public string[] Phones { get { return phones; } }
我们接下来做个测试:
string[] phones = { "029-88401100", "029-88500321" }; Address a = new Address("陕西", "西安", "710068", phones); Console.WriteLine(a.Phones[0]); // 输出: 029-88401100 string[] b = a.Phones; b[0] = "029-XXXXXXXX"; // 通过b修改了 Address的内容 Console.WriteLine(a.Phones[0]); // 输出: 029-XXXXXXXX
为了解决这个问题,可以在构造函数中对外部传递进来的数组进行深度复制:
public Address(string province, string city, string zip, string[] phones) { // 前面略... this.phones = new string[phones.Length]; phones.CopyTo(this.phones, 0); CheckZip(zip); // 验证格式 }