C#类型基础----不可变类型
来源:互联网 发布:mac 拷贝隐藏文件 编辑:程序博客网 时间:2024/06/05 17:43
C#类型基础----不可变类型
前言
前面说过string类型是一种特殊的引用类型,成为不可变类型.本次就为大家说下什么是不可变类型.
正文
假如要设计一个存储收信人地址的类型(Type),叫做Address,它包含了这样几个属性:
Province 省
City 市
Zip 邮编
如果要对Zip格式进行控制(必须全为数字,且为6位),那么可以在其中添加一个CheckZip()方法:
public struct Address { private string province; public string Province { get { return province; } set { province = value; } } private string city; public string City { get { return city; } set { city = value; } } private string zip; 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! "); } } public override string ToString() { return String.Format("Province: {0}, City: {1}, Zip: {2}", province, city, zip); } }
这里已经存在第一个问题:应该将Address定义为类(引用类型)还是结构(值类型)?
当定义一个类时,更多的是定义一系列相关的操作(或者叫行为,方法).类中也会包含字段和属性,但这些字段通常都是为类的方法所调用,而属性则常用于表示类的状态(比如StringBuilder的Length),类的能力(比如StringBilder的Capacity).而在定义一个结构时,通常仅仅用它来保存数据,而不提供方法,或者是仅提供对其自身操作或者转换的方法,而非对其他类型提供服务的方法.
因为Address不包含任何的方法,它仅仅是将Province,City,ZIp这样的三个数据组织起来成为一个独立的操作单元,所以最好将其声明为一个Struct而非Class.
因此,首先将Address声明为一个Struct而非Class
接下来使用一下刚刚创建的Address类型:
Address a = new Address(); a.Province = "山东"; a.City = "淄博"; a.Zip = "250205"; Console.WriteLine(a.ToString());
看上去没问题,但是回想一下类型的定义,在给Zip属性赋值的时候可能会抛出异常,所以还是把它放在一个try-catch中,同时向Zip赋一个错误的值,看一下会发生什么:
Address a = new Address(); a.Province = "山东"; a.City = "淄博"; a.Zip = "250205"; try { a.City = "长沙"; a.Zip = "12345"; a.Province = "湖南"; } catch (Exception) { } Console.WriteLine(a.ToString());
通过输出,咱们就能发现部分问题,首要的问题是出现了数据不一致的现象,当为Zip赋值的时候,因为发生了一场,所以对Zip以及其后的Province的赋值都失败了,但是对City的赋值时成功的.
即使在赋值Zip时没有发生异常,也会出现问题:在多线程情况下,当当前线程执行到修改了City为”湖南”,但还没有修改Zip和Province的时候(Zip仍为”250205”,Province仍为”山东”).如果此时其他线程访问类型实例a,那么也将会读取到不一致的数据.
现在已经知道了上面出现的为题,那么接下来就该引出咱们要输的两个概念了:常量性和原子性.
对象的原子性:对象的装填是一个整体,如果一个字段改变,其他字段也要同时做出相应改变.简单来说,就是要么不改变,要么全改.
对象的常量性:对象的状态一旦确定,就不能再次更改了.如果想要再次更改,需要重新构造一个对象.
接下来就是如何实现这两个概念了.对于原子性,实现的方法是添加一个构造函数,在这个构造函数中为对象的所有字段赋值.而为了实施常量性,不允许在为对象赋值以后还能对对象进行修改,所以讲属性中的set访问器删除,同时将字段声明为readonly:
public struct Address { private readonly string province; public string Province { get { return province; } } private readonly string city; public string City { get { return city; } } private readonly string zip; public string Zip { get { return zip; } } public Address(string province, string city, string zip) { this.province = province; this.city = city; this.zip = zip;CheckZip(zip); } private void CheckZip(string value) { string pattern = @"\d{6}"; if (!Regex.IsMatch(value, pattern)) { throw new Exception("Zip is invalid! "); } } public override string ToString() { return String.Format("Province: {0}, City: {1}, Zip: {2}", province, city, zip); } }
这样的话,当对Address对象进行创建时,将所有字段的赋值在构造函数中作为一个整体来进行;而当需要改变翻个字段的值时,也需要重新创建对象再赋值.示例如下:
Address a = new Address("山东","淄博","250205"); try { a = new Address("湖南","长沙","12345"); } catch (Exception) { throw; } Console.WriteLine(a.ToString());
上面的方法解决了数据不一致的问题,但是还是漏掉了一点:当类型内部维护着一个引用类型字段时,比如数组,尽管将它声明为readonly,在类型外部还是可以对它进行修改.现在修改Address类,为它添加一个数组phones,存储电话号码:
public struct Address { private readonly string province; public string Province { get { return province; } } private readonly string city; public string City { get { return city; } } private readonly string zip; public string Zip { get { return zip; } } private readonly string[] phones; public string[] Phones { get { return phones; } } public Address(string province, string city, string zip,string []phones) { this.province = province; this.city = city; this.zip = zip; this.phones = phones; CheckZip(zip); } private void CheckZip(string value) { string pattern = @"\d{6}"; if (!Regex.IsMatch(value, pattern)) { throw new Exception("Zip is invalid! "); } } public override string ToString() { return String.Format("Province: {0}, City: {1}, Zip: {2}", province, city, zip); } }
测试代码如下:
string[] phones = {"18753377***","13953134***"}; Address a = new Address("山东","淄博","250205",phones); Console.WriteLine(a.Phones[0]);//输出187的电话 string[] b = a.Phones; b[0] = "13869188***";//通过b修改了Address的内容 Console.WriteLine(a.Phones[0]);//输出138的电话
可以看到,尽管将phones字段声明为了readonly,并且它也值提供了get属性访问器.仍然可以通过Address对象a外部的变量b,修改了a对象内部的内容.如何避免这种情况呢?
可以采取上面说过的深度复制的方式来解决,在phones的get属性访问器中添加如下代码:
public string[] Phones { get { string[] rtn = new string[phones.Length]; phones.CopyTo(rtn,0); return rtn; } }
在get访问器中,创建一个新的数组,并将Address对象本身的数组内容进行了复制,然后返回给调用者.此时,再次运行刚才的代码,由于b指向了新创建的这个数组对象,而非Address对象a内部的数组对象,所以对b的修改不会影响到a.
但是,问题不会这么简单的就结束了,看下面这段代码:
string[] phones = {"18753377***","13953134***"}; Address a = new Address("山东","淄博","250205",phones); Console.WriteLine(a.Phones[0]);//输出187的电话 phones[0] = "13869188***";//通过phones变量修改了Address对象北部的数据 Console.WriteLine(a.Phones[0]);//输出138的电话
在创建完Address对象后,依然可以通过之前的数组变量来修改对象内部的数据,收到前面的启发,咱们可以在构造函数中对外部传递进来的数组进行深度复制:
public Address(string province, string city, string zip,string []phones) { this.province = province; this.city = city; this.zip = zip; this.phones = new string[phones.Length]; phones.CopyTo(this.phones, 0); CheckZip(zip); }
完美了!
小结
这一章关于C#基础类型的东西咱们就说这么多了,这几天,不对,这一个月吧,一直在看小说,可能把写博客,学习啊什么的忘了,嘻嘻,其实我也不想这样,一开始那本小说是5W页,我连着看了一个月,好不容易看完了,结构TMMD作者又写了一本这本小书的续集!7W页!真是够了!等楼主看完了,咱们在细细谈!对了小书的名字叫做<<铁器时代>>作者:骁骑校
- C#类型基础----不可变类型
- scala基础--->不可变类型操作
- 不可变类型
- python 可变不可变类型
- JAVA String类型不可变
- 可变类型和不可变类型
- python可变类型与不可变类型
- 为什么String类型是不可变的?
- python 可变 与 不可变类型
- String类型是不可变的
- python中的可变与不可变类型
- 使用volatile类型发布不可变对象
- String类型值不可变的原因
- String 类型不可变 如何理解?
- java中可变类型和不可变类型
- 2.1.7可变类型与不可变类型
- Java中可变类型和不可变类型
- python的可变类型和不可变类型
- Qt Creator下载和安装(详细教程)
- linux 下的phpstudy 添加 mysql 为环境变量
- 【Unity开发】UNITY实现断点续传
- ionic学习笔记1
- key/value数据库
- C#类型基础----不可变类型
- Yii的日志的处理机制以及扩展案例
- Thinking in Java 读书笔记(一) --对象导论
- HDU 1003 Max Sum (DP)
- json和object之间相互转换
- POJ1741&&BZOJ1468 男人八题
- 2015年总结
- Java 字符串索引indexOf
- 代理模式