Immutable(不可变)集合
不可变集合,顾名思义就是说集合是不可被修改的。集合的数据项是在创建的时候提供,并且在整个生命周期中都不可改变。
为什么要用immutable对象?immutable对象有以下的优点:
- 对不可靠的客户代码库来说,它使用安全,可以在未受信任的类库中安全的使用这些对象
- 线程安全的:immutable对象在多线程下安全,没有竞态条件
- 不需要支持可变性, 可以尽量节省空间和时间的开销. 所有的不可变集合实现都比可变集合更加有效的利用内存 (analysis)
- 可以被使用为一个常量,并且期望在未来也是保持不变的
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
- 过时但仍值得学习的选项卡TabHost
- 你所不知道的渗透测试:应用虚拟化的攻防
- C++中const小结
- 很多人不知道还有这个——搜索框组件SearchView
- 免费主题暗藏后门,波及WordPress等知名CMS系统
- 揭秘:针对PoS机的恶意软件工具箱
- 屏幕宽高不够,滚动视图ScrollView来凑
- 结合中间人攻击,Pidgin鸡肋漏洞变废为宝
- 日历视图CalendarView和定时器Chronometer
- 不用Linux也可以的强大文本处理方法
- 虚函数与虚继承寻踪
- AnalogClock、DigitalClock和TextClock时钟组件
- Sqlmap联合Nginx实现“地毯式”检测网站SQL注入漏洞
- 两分钟掌握数值选择器NumberPicker
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- ThreadLocal与InheritableThreadLocal
- 每天学点JavaScript基础(1)—— null 和 undefined
- 每天学点JavaScript基础(2)——JavaScript里的分号,你加还是不加?
- Flutter中async与await异步编程原理分析
- 在树莓派上使用360WIFI
- Orange PI Zero OpenWrt固件
- 定制用于日常使用的树莓派系统
- adb网络调试重启后不关闭得方法
- Mac版VsCode不能主动更新问题
- TensorFlow安装
- AQS原理分析
- 初步认识线程安全性
- GPU,CUDA,cuDNN的理解
- Spark Streaming——Spark第一代实时计算引擎
- nano使用