IMMUTABLE COLLECTIONS(1)

Immutable Collections1

/玄魂

前言

.NET4.0开始,到现在的4.5,我们可以感受得到微软在并行、多线程、异步编程上带给开发人员的惊喜。在多线程开发中,无可避免的涉及多个线程共享对象问题,Immutable Object(不可变对象)在保证线程安全方面的重要性被凸显出来。简单不可变对象,比如单例,我们可以很轻松的创建并维护,一些复杂对象,对象引用或者集合对象的场景 ,创建和维护不可变对象变得困难了很多。微软在这方面也做了很多努力,目前看最令我欣喜的就是Immutable Collections了。如果您了解函数式编程,那么对此肯定不会陌生。

当然除了线程安全,不可变集合还有其他的应用场景,本文也会有所涉及。

笔者最近研读了几篇MSDN Blog中关于Immutable Collections的英文博文(在文后会给出链接)。我看到的博客中的代码和我下载的版本有些出入,我根据自己的理解重新整理,改编成此文,水平有限,欢迎讨论。

1.1  Immutability OBJECT简单分类

真正的不可变对象

这类对象只能在编译时赋值,在C#const类型的变量属于这个类型。

一次初始化对象

运行时初始化一次,之后再也不会被改变。典型的单例对象就属于这一类。

浅度不变和深度不变

C#为例,对象本身是Static ReadOnly类型,但是这不能保证该对象内部成员是线程安全的,这类对象具有浅度不变性,如果能保证对象本身、对象内部任何成员或者嵌套成员都具有不变性则该对象具有深度不变性。

显然,具有深度不变性的对象是理想的线程安全模型。

1.2 安装和使用

不要误会安装的含义,这里是指从Nuget安装提供不可变集合功能的Dll

运行环境:vs2012.NET 4.5

PM> Install-Package Microsoft.Bcl.Immutable -pre

您正在从 Microsoft 下载 Microsoft.Bcl.Immutable,有关此程序包的许可协议在 http://go.microsoft.com/fwlink/?LinkID=272980&clcid=0x409 上提供。请检查此程序包是否有其他依赖项,这些依赖项可能带有各自的许可协议。您若使用程序包及依赖项,即构成您接受其许可协议。如果您不接受这些许可协议,请从您的设备中删除相关组件。

已成功安装“Microsoft.Bcl.Immutable 1.0.8-beta”

已成功将“Microsoft.Bcl.Immutable 1.0.8-beta”添加到。。

这个Preview版本的安装包包含了如下不可变类型:

·             ImmutableStack<T>

·             ImmutableQueue<T>

·             ImmutableList<T>

·             ImmutableHashSet<T>

·             ImmutableSortedSet<T>

·             ImmutableDictionary<K, V>

·             ImmutableSortedDictionary<K, V>

每种类型都继承自相应的接口,从而保证之后不可变类型的可扩展性。

先以ImmutableList<T>为例,开始我们的不可变集合之旅。

class Program

    {

        static void Main(string[] args)

        {

            ImmutableList<string> emptyBusket = ImmutableList.Create<string>();

        }

    }

注意上面的代码,我们没有使用构造函数来初始化ImmutableList<string>集合,而是使用名为ImmutableListCreate方法,该方法返回一个空的不可变集合。使用空集合在某些情况下可以避免内存浪费。

Create方法有7个重载,可以传入初始化数据和比较器。

下面我们尝试向这个集合中添加一些数据。

 class Program

    {

        static void Main(string[] args)

        {

            ImmutableList<string> emptyBusket = ImmutableList.Create<string>();

            var fruitBasket = emptyBusket.Add("apple");

        }

    }

我想您已经看到Immutable Collections和传统集合的一个区别 了,Add方法创建了一个新的集合。这里我们也可以使用AddRange方法批量添加数据创建新的实例。

1.3 Builders

有时,我们可能更需要对一个集合多次修改才能到达要求。从上面的示例我们知道,每次修改都会创建新的集合,这就意味着要开辟新的内存,并且存在数据拷贝。程序本身的执行效率会下降同时GC压力会增大。

其实同样的问题再String类型上也存在,反复的修改字符串值存在同样的问题,.NETStringBuilder用来解决这个问题。类似的IMMUTABLE COLLECTIONS也提供了Builder类型。同时我们具有了在迭代的同时修改集合的能力!

下面我们来看看Builder的基本应用:

    static void Main(string[] args)

        {

            ImmutableList<string> fruitBusket = ImmutableList.Create<string>("apple","orange","pear");

          

            var builder = fruitBusket.ToBuilder();

            foreach (var fruit in fruitBusket)

            {

                if (fruit == "pear")

                {

                    builder.Remove(fruit);

                }

            }

            builder.Add("ananas");

            fruitBusket = builder.ToImmutable();

            foreach (var f in fruitBusket)

            {

                Console.WriteLine(f);

            }

            Console.Read();

        }

在上面的代码中,使用ToBuilder方法获取Builder对象,在最后使用To ToImmutable方法返回IMMUTABLE COLLECTION。这里需要注意的是ToBuilder方法并没有拷贝资源给新Builder对象,Builder的所有操作都和集合共享内存。也许您要怀疑,既然是共享内存,那么Builder修改数据的时候集合怎么能不变化呢?这是因为ImmutableList的内部数据结构是树,只需要在更新集合的时候创建一个新的引用包含不同节点的引用即可。内部的实现原理,我会在下一篇博文中继续探讨。上面的代码既没有修改原来的IMMUTABLE COLLECTION也没有拷贝整个集合的内部操作。运行结果如下:

1.4  性能(Performance

immutable collections 在很多方面,性能优于可变集合。当然性能上的优势和可变还是不可变的关系并不大,主要原因在于immutable collections内部的数据结构。比如下面的代码:

    private List<T> collection;

    

     public IReadOnlyList<int> SomeProperty

     {

         get

         {

             lock (this)

             {

                 return this.collection.ToList();

             }

         }

     }

每次访问都会引起内存拷贝的操作。

但是如果使用immutable collection就可以避免这个问题:

private ImmutableList<T> collection;

    public IReadOnlyList<int> SomeProperty

    {

        get { return this.collection; }

    }

内部原理和对性能的影响放在下一篇博客探讨,下面的列表是在算法复杂度层面的对比:

 

Mutable (amortized)

Mutable (worst case)

Immutable

Stack.Push

O(1)

O(n)

O(1)

Queue.Enqueue

O(1)

O(n)

O(1)

List.Add

O(1)

O(n)

O(log n)

HashSet.Add

O(1)

O(n)

O(log n)

SortedSet.Add

O(log n)

O(n)

O(log n)

Dictionary.Add

O(1)

O(n)

O(log n)

SortedDictionary.Add

O(log n)

O(n log n)

O(log n)

在内存使用方面,Immutable Collections要比可变类型的集合要多,空间换时间,这个世界上没有两全其美的事情。

小结

本篇博文只是浅尝则止,从概念上为您介绍了Immutable Collections的基本定义,简单应用。

我并没有拿典型的应用场景来举例,但是您可以从它们的线程安全,性能,内存使用等特性上权衡使用。如果有机会,我会将我的实际应用场景分享给您。

接下来,在下一篇博客中,我会探讨Immutable Collections的内部原理。也许你现在不会使用.NET4.5,但是其内部原理却是和平台无关的。

参考资料:

http://blogs.msdn.com/b/bclteam/archive/2013/03/19/inner-workings-of-immutable-collections-on-channel-9.aspx

http://blogs.msdn.com/b/bclteam/archive/2012/12/18/preview-of-immutable-collections-released-on-nuget.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/11/13/immutability-in-c-part-one-kinds-of-immutability.aspx

http://blogs.msdn.com/b/andrewarnottms/archive/2011/08/22/read-only-frozen-and-immutable-types-and-collections.aspx

 

 

posted @ 2013-04-26 18:22  玄魂  阅读(3157)  评论(3编辑  收藏  举报