前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Immutable(不可变)集合

Immutable(不可变)集合

作者头像
张善友
发布2018-01-19 11:05:36
8910
发布2018-01-19 11:05:36
举报
文章被收录于专栏:张善友的专栏张善友的专栏

不可变集合,顾名思义就是说集合是不可被修改的。集合的数据项是在创建的时候提供,并且在整个生命周期中都不可改变。

为什么要用immutable对象?immutable对象有以下的优点:

  1. 对不可靠的客户代码库来说,它使用安全,可以在未受信任的类库中安全的使用这些对象
  2. 线程安全的:immutable对象在多线程下安全,没有竞态条件
  3. 不需要支持可变性, 可以尽量节省空间和时间的开销. 所有的不可变集合实现都比可变集合更加有效的利用内存 (analysis)
  4. 可以被使用为一个常量,并且期望在未来也是保持不变的

immutable对象可以很自然地用作常量,因为它们天生就是不可变的对于immutable对象的运用来说,它是一个很好的防御编程(defensive programming)的技术实践。

微软.NET团队已经正式发布了不可变集合,可以通过Nuget添加,包括了下面的不可变集合:

System.Collections.Immutable.ImmutableArray
System.Collections.Immutable.ImmutableArray<T>
System.Collections.Immutable.ImmutableDictionary
System.Collections.Immutable.ImmutableDictionary<TKey,TValue>
System.Collections.Immutable.ImmutableHashSet
System.Collections.Immutable.ImmutableHashSet<T>
System.Collections.Immutable.ImmutableList
System.Collections.Immutable.ImmutableList<T>
System.Collections.Immutable.ImmutableQueue
System.Collections.Immutable.ImmutableQueue<T>
System.Collections.Immutable.ImmutableSortedDictionary
System.Collections.Immutable.ImmutableSortedDictionary<TKey,TValue>
System.Collections.Immutable.ImmutableSortedSet
System.Collections.Immutable.ImmutableSortedSet<T>
System.Collections.Immutable.ImmutableStack
System.Collections.Immutable.ImmutableStack<T>

MSDN的文档参考 https://msdn.microsoft.com/zh-cn/library/system.collections.immutable.aspx ,怎么使用呢?我们来看一个例子,假设你已经建立了一个计费系统,你需要一个不可变的设计,在多线程操作的情况下不需要担心数据损坏。例如,你需要通过一个辅助线程打印数据的一个快照,这种方式避免阻塞用户的编辑操作,允许用户继续编辑而不影响打印。

可变的数据模型是这样:

class Order
{
     public Order()
     {
         Lines = new List<OrderLine>();
     }
 
     public List<OrderLine> Lines { get; private set; } 
 }
 
 class OrderLine
 {
     public int Quantity { get; set; }
     public decimal UnitPrice { get; set; }
     public float Discount { get; set; }
 
     public decimal Total
     {
         get
         {
          return Quantity * UnitPrice * (decimal) (1.0f - Discount);
         }
     }
 } 

下面我们把它转换为不可变的设计:

class OrderLine
{
     public OrderLine(int quantity, decimal unitPrice, float discount)
     {
         Quantity = quantity;
         UnitPrice = unitPrice;
         Discount = discount;
     }
 
     public int Quantity { get; private set; }
 
     public decimal UnitPrice { get; private set; }
 
     public float Discount { get; private set; }
 
     public decimal Total
     {
         get
         {
          return Quantity * UnitPrice * (decimal) (1.0f - Discount);
         }
     }
 } 

这种新设计要求您创建一个订单,每当任何属性值变化创建一个新实例。您可以通过添加 WithXxx 方法,使您可以更新单个属性而无需显式调用构造函数:

class OrderLine
{
    // ... 
    public OrderLine WithQuantity(int value)
    {
        return value == Quantity
                ? this
                : new OrderLine(value, UnitPrice, Discount);
    } 
    public OrderLine WithUnitPrice(decimal value)
    {
        return value == UnitPrice
                ? this
                : new OrderLine(Quantity, value, Discount);
    } 
    public OrderLine WithDiscount(float value)
    {
        return value == Discount
                ? this
                : new OrderLine(Quantity, UnitPrice, value);
    }
} 

这使得不可变使用起来比较简单:

OrderLine apple = new OrderLine(quantity: 1, unitPrice: 2.5m, discount: 0.0f); 
OrderLine discountedAppled = apple.WithDiscount(.3f); 

现在让我们看看我们如何落实订单的不变性。Lines 属性已经是只读的但它指的是可变对象。因为它是一个集合,它可以容易地通过简单地将它替换 ImmutableList <T>转换:

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

这种设计有一些有趣的属性:

• 该构造函数接受 IEnumerable <T>,允许传递任何集合中。

• 我们使用 ToImmutableList() 扩展方法,将转换为 ImmutableList <OrderLine>。如果该实例已经是不可变的列表,它会简单地转换而不是创建一个新的集合。

• 该 WithLines() 方法遵循 我们的订单公约,如果新的列表和当前列表是相同的就可以避免创建一个新的实例。

我们还可以加一些便利的方法来使它更易于更新订单行:

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));
    }
} 

增补订单的代码看起来是这样子:

OrderLine apple = new OrderLine(quantity: 1, unitPrice: 2.5m, discount: 0.0f);
Order order = new Order(ImmutableList.Create(apple)); 
OrderLine discountedApple = apple.WithDiscount(discount);
Order discountedOrder = order.ReplaceLine(apple, discountedApple); 

这种设计的好处是,它尽可能避免了不必要的对象创建。例如,当折扣的值等于 0.0 f,即时没有折扣,,discountedApple 和 discountedOrder 引用现有实例的苹果和订单。

这是因为:

1.apple.WithDiscount() 将返回苹果的现有实例,因为新的折扣是相同折扣属性的当前值。

2.order.ReplaceLine() 如果两个参数都相同,将返回现有实例。

我们不变的集合其他操作遵循这种最大化重用。例如,将订单行添加到 1000 的订单行的订单与 1,001 订单行不会创建整个的新列表。相反,它将重用现有列表一大块。这是可能的因为列表内部结构是为一棵树,允许共享不同实例的节点。

这里有两个视频介绍可变性集合:

 Immutable Collections for .NET

Inner workings of immutable collections

不可变集合的系列博客推荐:

Exploring the .NET CoreFX Part 9: Immutable Collections and the Builder 

Exploring the .NET CoreFX Part 13: ImmutableList is an AVL Tree

Exploring the .NET CoreFX Part 14: Inside Immutable Collections

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2015-09-05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档