国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

探索c#之不可變數據類型

2019-11-17 02:29:19
字體:
來源:轉載
供稿:網友

探索c#之不可變數據類型

2015-03-28 14:44 by 蘑菇先生, ... 閱讀, ... 評論, 收藏, 編輯

閱讀目錄:

  1. 不可變對象
  2. 自定義不可變集合
  3. Net提供的不可變集合
  4. 不可變優點
  5. 不可變對象缺點

不可變對象

不可變(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<T> : IEnumerable<T>{    IStack<T> Push(T value);    IStack<T> Pop();    T Peek();    bool IsEmpty { get; }}public sealed class Stack<T> : IStack<T>{    private sealed class EmptyStack : IStack<T>    {        public bool IsEmpty { get { return true; } }        public T Peek() { throw new Exception("Empty stack"); }        public IStack<T> Push(T value) { return new Stack<T>(value, this); }        public IStack<T> Pop() { throw new Exception("Empty stack"); }        public IEnumerator<T> GetEnumerator() { yield break; }        IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }    }    private static readonly EmptyStack empty = new EmptyStack();    public static IStack<T> Empty { get { return empty; } }    private readonly T head;    private readonly IStack<T> tail;    private Stack(T head, IStack<T> tail)    {        this.head = head;        this.tail = tail;    }    public bool IsEmpty { get { return false; } }    public T Peek() { return head; }    public IStack<T> Pop() { return tail; }    public IStack<T> Push(T value) { return new Stack<T>(value, this); }    public IEnumerator<T> GetEnumerator()    {        for (IStack<T> stack = this; !stack.IsEmpty; stack = stack.Pop())            yield return stack.Peek();    }    IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }}
View Code
  • 入棧時會實例化一個新棧對象
  • 將新值通過構造函數傳入,并存放在新對象Head位置,舊棧對象放在在Tail位置引用
  • 出棧時返回當前棧對象的Tail引用的棧對象

使用方法如下:

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

每次Push都是一個新對象,舊對象不可修改,這樣在枚舉集合就不需要擔心其他線程修改了。

Net提供的不可變集合

不可變隊列,不可變列表等數據結構如果都自己實現工作量確實有點大。幸好的是Net在4.5版本已經提供了不可變集合的基礎類庫。 使用Nuget安裝:

Install-Package Microsoft.Bcl.Immutable

使用如下,和上面我們自定義的幾乎一樣:

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

使用Net不可變列表集合有一點要注意的是,當我們Push值時要重新賦值給原變量才正確,因為push后會生成一個新對象,原a1只是舊值:

   ImmutableStack<int> a1 = ImmutableStack<int>.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<string> immutable = ImmutableList<string>.Empty;        //轉換成可批量操作的集合        var immutable2 = immutable.ToBuilder();        immutable2.Add("xx");        immutable2.Add("xxx");        //還原成不可變集合        immutable = immutable2.ToImmutable();

我們來對比下可變集合、不可變Builder集合、不可變集合的性能,添加新對象1000W次:

比較代碼如下:

   private static void List()        {            var list = new List<object>();            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<object>.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<object>.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(...)


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 诸暨市| 墨玉县| 沿河| 弥渡县| 贵阳市| 安西县| 时尚| 若羌县| 武功县| 陆良县| 靖西县| 广西| 玉门市| 五台县| 宁陵县| 邵东县| 扎囊县| 上虞市| 五寨县| 卓尼县| 玉树县| 新邵县| 南安市| 无锡市| 上杭县| 南靖县| 华坪县| 成武县| 文化| 福清市| 台北县| 万安县| 清水县| 彰武县| 县级市| 保德县| 黄大仙区| 平谷区| 尼勒克县| 谢通门县| 青冈县|