前几天我的一个同事在和我交流的时候,提到了这样一个问题:为什么在DateTime里面的一堆AddXXX的成员方法不是作用在实例本身,而是另外返回一个重新实例化的DateTime,在通常的理解上我们看到一个对象的AddXXX的实例方法的时候,第一感觉应该是作用在本身上,即是把本身某个字段的值改变成运算后的值。可是在这里却不遵循这样的“常理”。这样在用的时候,想把一个DateTime的值加上一定的时间的话,很容易就忘了需要重新执行赋值操,导致程序执行的结果和我们预期的不一样。
我听了之后也同意他的看法,因为自己在写程序的时候,也因为经常忘了这一点而发生类式的“BUG”。回头仔细想了想这个问题,发现类似的还不止DateTime一个,像TimeSpan,DateTimeOffset,甚至于是String的Replace等都是这样的一种情况。非public的方法咱就不说了,没有对外公布里面怎么使用都还可以说得过去,可对外公开的方法还是这样的,就有些不太理解了。
从上面提到的类型不难看出,这些大都是结构,也就是值类型。虽然String类本身是类类型,但是根据MSDN的说法,由于考虑到在使用上的习惯,String在大部分场合的行为都与值类型一至,因此就行为上来看,Sring大部分的行为仍然是符合值类型的行为,包括赋值,比较,参数传递等。至于为什么String的Replace不是作用在本身上,而是返回一个新的String实例,还有一个原因是String实例分配的内存在构造完成后是只读的,因此想要改变这个值就只能重新生成一个新的实例了。String类的这个方法弄明白了后我们再回到之前提到的那些结构上。这些结构的内部都是由一些简单的值类型组成的,而且从声明的成员字段上来看,都是可以随意改变的。
2 {
3 // Fields
4 private DateTime m_dateTime;
5 private short m_offsetMinutes;
6
7 }
8
9 public struct DateTime
10 {
11 // Fields
12 private ulong dateData;
13
14 }
15
16 public struct TimeSpan
17 {
18
19 internal long _ticks;
20
21 }
22
2 {
3 this.X += dx;
4 this.Y += dy;
5 }
6 public void Offset(Point p)
7 {
8 this.Offset(p.X, p.Y);
9 }
另外还有更“神奇”的是同样是Point(F)里的方法Add却变成了一个静态的方法,而且是重新构造了一个新的Point(F)结构返回,我在这里一点也看不出来为什么这个地方要把Add声明成静态的而不是Point的一个成员方法,功能上这个方法和Offset的那两个方法没有本质上的区别。
2 {
3 return new Point(pt.X + sz.Width, pt.Y + sz.Height);
4 }