心如止水

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

参考自:http://www.cnblogs.com/fox23/archive/2008/07/26/understanding-immutable-in-csharp.html

有一种很简单也很受用的编程(不仅仅是C#)宗旨,就是所谓的"Immutability"(不可变性质)。如果一个类的实例是immutable的,那么我们把这个类也称作immutable class。

这样说来,似乎immutable的确是一个相当简单的东西,不过从以下几个问题中你可以找到使用immutable对象的便利之处。我们可以想一下,为什么编写一个多线程的应用程序要相对困难一些?那是因为在访问某些资源(对象或者其他OS掌管的资源)的时候线程间的同步问题总是会令人感到头疼。那为什么会有线程访问同步的问题呢?那是因为在多线程多个对象之间,要保证他们的的多个读和写的操作不会引起冲突是一件很困难的事。那么这些冲突为什么会造成我们不希望的结果呢,其实关键就在于这里的“写”操作,因为只有它会改变对象的状态,给我们带来非预计的结果。

System.String

还是从这个“知名”的Immutable class开始谈吧,这个经常使用的类型被设计为immutable,当你改变一个String对象的时候,一个新的对象副本将被创建。尽管几乎所有的 C#教科书都会谈及这个问题,但是有时候我们似乎并不在意,于是我们经常会编写类似这样的语句:

 

 string str = "cnblogs";
string newstr = str.Replace(
"cn""CN");

这里的str本身并没有改变,只是创建了一个"CNblogs"的副本。 

很显然,对string频繁进行这样的操作会在内存中制造N多string对象,多数情况下那并不是我们所希望的。当然,这时候我们知道可以用System.Text.StringBuilder这样一个安全的方式来构造可变的字符串对象。

OK,上述内容几乎所有C#语言相关书籍上的说法都是一致的。但是String真的是完全immutable的么?

我想,这个倒未必哦,至少有这么几个方式是可以使得String不那么immutable的:   

1. 直接操作指针

public class Program {
   
static unsafe void ToUpper( string str ) {
      
fixed ( char* pfixed = str )
         
for ( char* p = pfixed; *!= 0; p++ )
            
*= char.ToUpper(*p);
   }

   
static void Main() {
      
string str = "Hello";
      System.Console.WriteLine(str);
      ToUpper(str);
      System.Console.WriteLine(str);
   }

}
注意,如果调用str[0] = 'C'是不对的,因为str[0]是只读的。

2. 使用反射

 m_stringLength:This is the logical length of the string, the one returned by String.Length.

System.Reflection also allows programmers to access hidden fields, members, and properties--yes, even those marked with internal and private. This reflection-based approach performs much more slowly than the manual approach above, but does not require unsafe code and should be more robust in the face of changing versions of the runtime. The line demonstrates the ability to change the length of an immutable string through reflection:
 
  string str = "cnblogs";
typeof(string).GetField("m_stringLength",
BindingFlags.NonPublic
|BindingFlags.Instance).SetValue(s, 5);
  执行完后,str的值是"cnblo"。
 
为什么String要被设计为immutable呢?正如前面提到的那样,因为immutable使得程序员在对string使用上不至于陷入竞态条件 (race condition)。另外,也因为这样的String很适于在hashtable/Dictionary<K,V>中做key,因为只有 immutable的对象作为hash的键,才能保证hash值始终为常量。当然,通常hash的值是从对象的某些状态(或者子状态)计算而来,而对象的这些状态(子状态)应为immutable。

从以上对String的讨论中我们至少可以得到以下几条immutable的优势

  • 便于多线程编程
  • 方便地作为hashtable的key
  • 便于比较状态

不过我还是想提醒一下,immutable还是有副作用的,就比如之前提到的产生很多垃圾对象。

C#中immutable的实现

1. 经典的immutable class

class Contact
{
    
public Contact(String fullName, String phoneNumber)
    
{
        
this.fullName= fullName;
        
this.phoneNumber= phoneNumber;
    }


    
public Contact ChangeNumber(String newNumber)
    
{
        
//创建一个新实例
        return new Contact (this.fullName, newNumber);
    }


    
readonly String fullName;
    
public String FullName get return fullName; }}

    
readonly String phoneNumber;
    
public uint PhoneNumberget return phoneNumber; }}
}

这个例子几乎无须再解释,每次changeNumber的时候就构造一个新的Contact对象。

C# 对immutability的支持离不开这两个关键字: constreadonly

2. C#3.0中的immutable 

C#3的编译器生成的匿名类型是immutable的,所有的字段都是private的,所有的属性都是只能get(使用reflector可以看到)。下面的代码将会在编译时报错:“Error 1 Property or indexer 'AnonymousType#1.A' cannot be assigned to -- it is read only ....”

        static void Main()
        
{
            var ab 
= new {A=1,B=2 };
            ab.A 
= 3;
        }
  

posted on 2012-04-20 15:20  cutebear  阅读(868)  评论(0编辑  收藏  举报