博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
探索c#之不可变数据类型
阅读量:5884 次
发布时间:2019-06-19

本文共 6288 字,大约阅读时间需要 20 分钟。

阅读目录:

不可变对象

不可变(immutable): 即对象一旦被创建初始化后,它们的值就不能被改变,之后的每次改变都会产生一个新对象。

var str="mushroomsir";str.Substring(0, 6)

c#中的string是不可变的,Substring(0, 6)返回的是一个新字符串值,而原字符串在共享域中是不变的。另外一个StringBuilder是可变的,这也是推荐使用StringBuilder的原因。

var age=18;

当存储值18的内存分配给age变量时,它的内存值也是不可以被修改的。

age=2;

此时会在栈中开辟新值2赋值给age变量,而不能改变18这个内存里的值,int在c#中也是不可变的。

class Contact{     public string Name { get;  set; }    public string Address { get;  set; }    public Contact(string contactName, string contactAddress)    {        Name = contactName;        Address = contactAddress;                   }}   var mutable = new Contact("二毛", "清华");   mutable.Name = "大毛";   mutable.Address = "北大";

我们实例化MutableContact赋值给mutable,随后我们可以修改MutableContact对象内部字段值,它已经不是初始后的值,可称为可变(mutable)对象。

可变对象在多线程并发中共享,是存在一些问题的。多线程下A线程赋值到 Name = "大毛" 这一步,其他的线程有可能读取到的数据就是:

mutable.Name == "大毛";  mutable.Address == "清华";

很明显这样数据完整性就不能保障,也有称数据撕裂。我们把可变对象更改为不可变对象如下:

public class Contact2{    public string Name { get; private set; }    public string Address { get; private set; }    private Contact2(string contactName, string contactAddress)    {        Name = contactName;        Address = contactAddress;                   }    public static Contact2 CreateContact(string name, string address)    {        return new Contact2(name, address);    }}

使用时只能通过Contact2的构造函数来初始化Name和Address字段。Contact2此时即为不可变对象,因为对象本身是个不可变整体。通过使用不可变对象可以不用担心数据完整性,也能保证数据安全性,不会被其他线程修改。

自定义不可变集合

我们去枚举可变集合时,出于线程安全的考虑我们往往需要进行加锁处理,防止该集合在其他线程被修改,而使用不可变集合则能避免这个问题。我们平常使用的数据结构都是采用可变模式来实现的,那怎么实现一个不可变数据结构呢!以栈来示例,具体代码如下:

public interface IStack
: IEnumerable
{ IStack
Push(T value); IStack
Pop(); T Peek(); bool IsEmpty { get; }}public sealed class Stack
: IStack
{ private sealed class EmptyStack : IStack
{ public bool IsEmpty { get { return true; } } public T Peek() { throw new Exception("Empty stack"); } public IStack
Push(T value) { return new Stack
(value, this); } public IStack
Pop() { throw new Exception("Empty stack"); } public IEnumerator
GetEnumerator() { yield break; } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } private static readonly EmptyStack empty = new EmptyStack(); public static IStack
Empty { get { return empty; } } private readonly T head; private readonly IStack
tail; private Stack(T head, IStack
tail) { this.head = head; this.tail = tail; } public bool IsEmpty { get { return false; } } public T Peek() { return head; } public IStack
Pop() { return tail; } public IStack
Push(T value) { return new Stack
(value, this); } public IEnumerator
GetEnumerator() { for (IStack
stack = this; !stack.IsEmpty; stack = stack.Pop()) yield return stack.Peek(); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }}
View Code
  • 入栈时会实例化一个新栈对象
  • 将新值通过构造函数传入,并存放在新对象Head位置,旧栈对象放在在Tail位置引用
  • 出栈时返回当前栈对象的Tail引用的栈对象

使用方法如下:

IStack
s1 = Stack
.Empty;IStack
s2 = s1.Push(10);IStack
s3 = s2.Push(20);IStack
s4 = s3.Push(30);IStack
v3 = s4.Pop();foreach (var item in s4){//dosomething}

每次Push都是一个新对象,旧对象不可修改,这样在枚举集合就不需要担心其他线程修改了。

Net提供的不可变集合

不可变队列,不可变列表等数据结构如果都自己实现工作量确实有点大。幸好的是Net在4.5版本已经提供了不可变集合的基础类库。 使用Nuget安装:

Install-Package Microsoft.Bcl.Immutable

使用如下,和上面我们自定义的几乎一样:

ImmutableStack
a1 = ImmutableStack
.Empty; ImmutableStack
a2 = a1.Push(10); ImmutableStack
a3 = a2.Push(20); ImmutableStack
a4 = a3.Push(30); ImmutableStack
iv3 = a4.Pop();

使用Net不可变列表集合有一点要注意的是,当我们Push值时要重新赋值给原变量才正确,因为push后会生成一个新对象,原a1只是旧值:

ImmutableStack
a1 = ImmutableStack
.Empty; a1.Push(10); //不正确,a1仍是空值值,push会生成新的栈。 a1 = a1.Push(10); //需要将新栈重新赋值给a1

NET提供的常用数据结构

  • ImmutableStack
  • ImmutableQueue
  • ImmutableList
  • ImmutableHashSet
  • ImmutableSortedSet
  • ImmutableDictionary<K, V>
  • ImmutableSortedDictionary<K, V>

不可变集合和可变集合在算法复杂度上的不同:

不可变优点

  • 集合共享安全,从不被改变
  • 访问集合时,不需要锁集合(线程安全)
  • 修改集合不担心旧集合被改变
  • 书写更简洁,函数式风格。 var list = ImmutableList.Empty.Add(10).Add(20).Add(30);
  • 保证数据完整性,安全性

不可变对象缺点

不可变本身的优点即是缺点,当每次对象/集合操作都会返回个新值。而旧值依旧会保留一段时间,这会使内存有极大开销,也会给GC造成回收负担,性能也比可变集合差的多。

跟string和StringBuild一样,Net提供的不可变集合也增加了批量操作的API,用来避免大量创建对象:

ImmutableList
immutable = ImmutableList
.Empty; //转换成可批量操作的集合 var immutable2 = immutable.ToBuilder(); immutable2.Add("xx"); immutable2.Add("xxx"); //还原成不可变集合 immutable = immutable2.ToImmutable();

我们来对比下可变集合、不可变Builder集合、不可变集合的性能,添加新对象1000W次:

比较代码如下:

private static void List()        {            var list = new List();            var sp = Stopwatch.StartNew();            for (int i = 0; i < 1000 * 10000; i++)            {                var obj = new object();                list.Add(obj);            }            Console.WriteLine("可变列表集合:"+sp.Elapsed);        }              private static void BuilderImmutableList()        {            var list = ImmutableList.Empty;            var sp = Stopwatch.StartNew();            var blist= list.ToBuilder();            for (int i = 0; i < 1000 * 10000; i++)            {                var obj = new object();                blist.Add(obj);            }            list=blist.ToImmutable();            Console.WriteLine("不可变Builder列表集合:"+sp.Elapsed);        }        private static void ImmutableList()        {            var list = ImmutableList.Empty;            var sp = Stopwatch.StartNew();            for (int i = 0; i < 1000 * 10000; i++)            {                var obj = new object();                list = list.Add(obj);            }            Console.WriteLine("不可变列表集合:" + sp.Elapsed);        }
View Code

另外一个缺点比较有趣,也有不少人忽略。 由于string的不可变特性,所以当我们使用string在保存敏感信息时,就需要特别注意。

比如密码 var pwd="mushroomsir",此时密码会以明文存储在内存中,也许你稍后会加密置空等,但这都是会生成新值的。而明文会长时间存储在共享域内存中,任何能拿到dump文件的人都可以看到明文,增加了密码被窃取的风险。当然这不是一个新问题,net2.0提供的有SecureString来进行安全存储,使用时进行恢复及清理。

IntPtr addr = Marshal.SecureStringToBSTR(secureString);string temp = Marshal.PtrToStringBSTR(addr);Marshal.ZeroFreeBSTR(addr);WriteProcessMemory(...)

 

转载地址:http://yslix.baihongyu.com/

你可能感兴趣的文章
jquery css3问卷答题卡翻页动画效果
查看>>
$digest already in progress 解决办法——续
查看>>
mysql 数据类型
查看>>
Ubuntu 设置当前用户sudo免密码
查看>>
ionic 调用手机的打电话功能
查看>>
怎么使用阿里云直播服务应用到现在主流直播平台中
查看>>
判断点是否在三角形内
查看>>
知行合一
查看>>
jmeter插件之jsonpath提取响应结果和做断言
查看>>
[Contiki系列论文之1]Contiki——为微传感器网络而生的轻量级的、灵活的操作系统...
查看>>
Android 网络编程 记录
查看>>
微软同步发行Windows 10和Windows 10 Mobile系统更新
查看>>
Zeppelin的入门使用系列之使用Zeppelin运行shell命令(二)
查看>>
form表单下的button按钮会自动提交表单的问题
查看>>
那些年追过的......写过的技术博客
查看>>
python基础教程_学习笔记19:标准库:一些最爱——集合、堆和双端队列
查看>>
CSS魔法堂:Transition就这么好玩
查看>>
解决win7远程桌面连接时发生身份验证错误的方法
查看>>
C/C++ 多线程机制
查看>>
Boost在vs2010下的配置
查看>>