zoukankan      html  css  js  c++  java
  • Immutable(不可变)集合

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

    为什么要用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

  • 相关阅读:
    RAID
    变量的内存位置
    OSI网络结构的七层模型 TCP/IP层次模型
    IT公司【应聘】
    ajax的一个最简单例子
    优先级反转问题
    问一道算法题:算出这些直线一共有多少个交点
    一个女研究生(高级测试工程师)的职业选择 ZZ
    使用SWIG实现C/C++与其他语言间的互相调用 zz
    创建系统级热键 C++ builder为例
  • 原文地址:https://www.cnblogs.com/shanyou/p/4782762.html
Copyright © 2011-2022 走看看