首先来解释一下标题,原标题为《Prefer Immutable Atomic Value Type》,因此对于标题的理解要分成三部分,第一部分为不可改变,第二部分为原子,最后一个部分为值类型。最后一部分,我不多说了,限制此章适用的范围。对于什么是不可改变类型,这里的意思是指此类型的变量一旦产生其成员就不能发生变化。至于原子类型,我以前在CSDN也经常提到,例如保证操作的原子性之类的语句,那么一个原子类型,其的子成员为不可分割的一部分,不能单独被操作。
听了标题解释,难免有些人会问,为什么要加上这样的限制,或者说这样做的好处是什么。为了解开这个疑团,我用一个例子来说明,去定义一个电话号码的值类型,一般形式如下:
public struct Phone
{
private string strCountry_Code;
private string strCity_Code;
private string strPhone_Number;
public string Country_Code
{
get{ return strCountry_Code; }
set{ strCountry_Code = value;}
}
public string City_Code
{
get{ return strCity_Code; }
set{ strCity_Code = value;}
}
public string Phone_Number
{
get{ return strPhone_Number; }
set{ strPhone_Number = value;}
}
// Constructor
public Phone( string sCountry_Code, string sCity_Code, string sPhone_Number )
{
strCountry_Code = sCountry_Code;
strCity_Code = sCity_Code;
strPhone_Number = sPhone_Number;
}
}
这样去初始化一个Phone类型变量的话,同时也可以做类似如下的相关操作。
Phone myPhone = new Phone( "086", "010", "66666666" );
myPhone.City_Code = "021";
myPhone.Phone_Number = "777777777";
大多数人觉得如上的代码没有什么问题,不过稍微明眼的人看了如上的代码,就会立刻觉得有潜在的危险。作为一个Phone类型变量来说,国家区号,城市区号,以及电话号码来说是一个整体。因此动态修改其中的某一个值,会造成其他两个无效。也就是说如上的代码直接修改City_Code的时候,对于myPhone来说其他两个变量Country_Code以及Phone_Number来说,此时是无效的。不过这种错误在单线程中不是很明显,但是在多线程中是致命的,而且很难查出来。有人可能会说了,在修改的时候加上Lock语句或者互斥标识来避免。这样是可以避免,但是试问一下,类型是你创建的,你怎么要求别人在使用你这个类型的时候做过多的操纵呢,为什么你不在创建此类型的时候就直接把这条路堵死。如果明白了这一点,就理解了这篇文章推荐的目的,即与其后期增加代码弥补,不如在前期就编写正确的代码。
知道这样做的原因,接下来就是如何去实现。不过在实现之前,要区分什么样的数据类型可以定义成不可变的原子类型。也就是说,你如何区分一个类型是一个整体,而且每个分支不能独立于整体而存在。例如对于联系方式这个类型来说,它包括电话号码、住址等等。它可以看为一个整体,但是分支可以脱离这个整体而存在,因此它不是一个原子类型。对于如何具体区分,很难有一个统一的方法,毕竟适应的环境不同,操作以及实现也不同。不过对于原子类型,有一个唯一判断方式,就是每个分支能否独立于整体而被操作,这个的是与否决定是否为原子类型。
那么如何去定义一个不可变的原子值类型呢,大致要对原有的类型做两个处理,一个就是把所有成员加上readonly标示,即只能在构造函数中被修改;另一个就是删除属性set部分。对于Phone这个类型来说,经过处理后,正确的形式如下:
public struct Phone
{
private readonly string strCountry_Code;
private readonly string strCity_Code;
private readonly string strPhone_Number;
public string Country_Code
{
get{ return strCountry_Code; }
}
public string City_Code
{
get{ return strCity_Code; }
}
public string Phone_Number
{
get{ return strPhone_Number; }
}
// Constructor
public Phone( string sCountry_Code, string sCity_Code, string sPhone_Number )
{
strCountry_Code = sCountry_Code;
strCity_Code = sCity_Code;
strPhone_Number = sPhone_Number;
}
}
这样对于一个Phone类型变量,只能通过new来创建(也就是说在输入三个有效的数据后,一个Phone类型变量才能产生)。
在此有人会问,除了new是否还有其他方法来进行修改。这是没有任何问题的,首先你只要理解了原子类型的意义,保证分支不会单独被修改即可,因此可以实现类似于“Phone.Parse”或者“Phone.From”之类的函数来形成一个新的Phone变量。
在实现不可变的原子值类型的时候,要防止类型中包括引用类型分支的时候,在进行成员赋值的时候,防止浅copy,这方面我就不多说了,参看我以前写的文章就可以明白(这里的目的也就是一点,防止局部破坏原子类型的分支)。
http://blog.csdn.net/Knight94/archive/2006/07/01/861383.aspx
http://blog.csdn.net/Knight94/archive/2006/06/04/772886.aspx