BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

.NET不可变集合已经正式发布

| 作者 Jonathan Allen 关注 530 他的粉丝 ,译者 邵思华 关注 3 他的粉丝 发布于 2013年10月1日. 估计阅读时间: 6 分钟 | Google、Facebook、Pinterest、阿里、腾讯 等顶尖技术团队的上百个可供参考的架构实例!

微软基础类库(Base Class Library)团队已经完成了.NET不可变集合的正式版本,但不包括ImmutableArray。与其一起发布的还包括针对其它不可变对象类型的设计指南。

如果你需要在多个线程中安全地共享集合,并且允许每个线程在需要时对其内容进行改变。这种场景就是不可变集合所设计的初衷。只读集合在使用时需要复制集合中的全部内容,而新的不可变集合可以以一种更高性能的方式从一个现有集合中进行创建。

使用不可变集合需要特别当心,因为你很容易错误地写成“list.Add(item)”,而正确的方法是“list = list.Add(item)”。甚至编译器也可能产生类似的错误,这也是为什么不可变集合不支持构造函数的原因。考虑以下代码:

list = new ImmutableList<int> {1, 2, 3};

在编译后会产生以下代码:

temp = new ImmutableList(); temp.Add(1); temp.Add(2) temp.Add(3) list = temp;

由于3次Add方法的结果都被丢弃,最终整个集合包含的项数目为0,而不是期望中的3。

不可变对象指南

Immo Lendwerth建议,当你在创建自己的不可变对象时,在其中加入适当的WithXxx方法。对简单的对象来说,为每一个属性创建一个WithXxx方法即可。当属性值需要变化时,该方法会返回当前对象的一个拷贝。

如果某属性代表了一个结合,那么这种模式就需要一点变化。以下这段代码来自Immo的发布声明

class Order
{
    public Order(IEnumerable<OrderLine> lines)
    {
        Lines = lines.ToImmutableList();
    }
    public ImmutableList<OrderLine> Lines { get; private set; }
    public Order WithLines(IEnumerableOrderLine> value)
    {
        return Object.ReferenceEquals(Lines, value)
            ? this
            : new Order(value);
    }
}

如你所见,WithLines方法可接受任意IEnumerable。因此你可以传递一个新创建的ImmutableList对象,或者是某个LINQ表达式的结果。这种方式已经足以满足需求了,不过他还建议提供某些辅助方法:

class Order
{
    //...
    public Order AddLine(OrderLine value)
    {
        return WithLines(Lines.Add(value));
    }
    public Order RemoveLine(OrderLine value)
    {
        return WithLines(Lines.Remove(value));
    }
    public Order ReplaceLine(OrderLine oldValue, OrderLine newValue)
    {
        return oldValue == newValue
                ? this
                : WithLines(Lines.Replace(oldValue, newValue));
    }
}

ImmutableArray被移除

由于性能方面的原因,ImmutableArray从最终的发布版本中被移除。其原因是:为了满足内存性能指标,ImmutableArray必须设计成一个值对象,并且为了保持值对象的语义,ImmutableArray的默认实例必须表现为一个空数组形式。不幸的是,为了达到这一点,对空值的检测(null check)会使得C#无法移除对数组边界的检测,而这一点是为达到良好CPU性能的一个重要考虑事项。

由于ImmutableArray类对于Roslyn编译器项目非常重要,设计者曾考虑删除会导致性能问题的空值检测功能,但又因此产生了另外的问题,Immo这样写道

由于所有的值类型都有一个自动产生的默认构造函数,它会将该值类型初始化为它的默认状态,而ImmutableArray<T>的默认值是空,它的底层数组实现则为null。因此,AddRange方法的实现会因为NullReferenceException的产生而崩溃。

这一问题还表现在其它一些地方,由于ImmutableArray<T>实现了某些集合接口(例如IEnumerable和IReadOnlyList),因此你可以把它传递给某些接受这种接口的方法。由于这种接口引用是非空的,使用者在调用它的方法或者属性时不会考虑到有可能产生NullReferenceException。

基础类库团队并未放弃这个项目,他们还在研究其它设计方式,以争取让ImmutableArray重新亮相。

查看英文原文:.NET Immutable Collections Ready for Production

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

开发工具究竟应该怎样帮助开发者? by china tech

“所有的值类型都有一个自动产生的默认构造函数,它会将该值类型初始化为它的默认状态”,微软做了太多“帮助”大家的事情,但实际上这些年来,开发工具并未对开发者有一个根本性的变革。看看vs2002和现在的vs2012,改变的是什么呢?

Re: 开发工具究竟应该怎样帮助开发者? by tsui jacky

一个是语言,一个是工具。02到12的改变,最显著的就是界面友好度和开发效率的提升

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

2 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT