中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

.NET基礎(chu)拾遺(yi)(3)字符(fu)串(chuan)、集合(he)和流

Index:

(1)類型語法、內存管理和垃圾回收基礎

(2)面向對象的實現和異常的處理

(3)字符(fu)串、集合(he)與(yu)流

(4)委托、事件、反射與特性

(5)多線程開發基礎

(6)ADO.NET與數據庫開發基礎

(7)WebService的開發與應用基礎

一、字符串處理

1.1 StringBuilder類型有什么作用?

  眾所周知,在.NET中String是引用類型,具有不可變性,當一個String對象被修改、插入、連接、截斷時,新的String對象就將被分配,這會直接影響到性能。但在實際開發中經常碰到的情況是,一個String對象的最終生成需要經過一個組裝的過程,而在這個組裝過程中必將會產生很多臨時的String對象,而這些(xie)String對(dui)象將(jiang)會在堆上(shang)分(fen)配,需要GC來回收,這些(xie)動(dong)作都會對(dui)程序性能產生巨(ju)大(da)的(de)(de)影(ying)響(xiang)。事實上(shang),在String的(de)(de)組裝過程中,其臨時產生的(de)(de)String對(dui)象實例都不是最終(zhong)需要的(de)(de),因(yin)此可以說是沒有必要分(fen)配的(de)(de)。

  鑒于此,在.NET中提供了StringBuilder,其設計思想源于構造器(Builder)設計模式,致力于解決復雜對象的構造問題。對于String對象,正需要這樣的構造器來進行組裝。StringBuilder類型在最終生成String對象之前,將不會產生任何String對象,這很好地解決了字符串操作的性能問題

  以(yi)下代碼展示了使(shi)(shi)用(yong)(yong)StringBuilder和不適(shi)用(yong)(yong)StringBuilder的(de)性能(neng)差(cha)異(yi):(這里(li)的(de)性能(neng)檢測工具使(shi)(shi)用(yong)(yong)了老(lao)趙(zhao)的(de)類)

    public class Program
    {
        private const String item = "一(yi)個項目(mu)";
        private const String split = ";";

        static void Main(string[] args)
        {
            int number = 10000;
            // 使用(yong)StringBuilder
            CodeTimer.Time("使用StringBuilder: ", 1, () =>
            {
                UseStringBuilder(number);
            });
            // 不使用(yong)StringBuilder
            CodeTimer.Time("使(shi)用(yong)不(bu)使(shi)用(yong)StringBuilder: : ", 1, () =>
            {
                NotUseStringBuilder(number);
            });

            Console.ReadKey();
        }

        static String UseStringBuilder(int number)
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            for (int i = 0; i < number; i++)
            {
                sb.Append(item);
                sb.Append(split);
            }
            sb.Remove(sb.Length - 1, 1);
            return sb.ToString();
        }

        static String NotUseStringBuilder(int number)
        {
            String result = "";
            for (int i = 0; i < number; i++)
            {
                result += item;
                result += split;
            }
            return result;
        }
    }
View Code

  上述代碼的運行結果如下圖所示,可(ke)以(yi)看出由于StringBuilder不(bu)會(hui)產生(sheng)任何的中(zhong)間字符串變量(liang),因(yin)此效率上優秀(xiu)不(bu)少!

  看到StringBuilder這么優秀,不禁想發出一句:臥槽,牛逼!

  于是,我們拿起我們的錘子(Reflector)撕碎StringBuilder的外套,看看里面到底裝了什么?我們發現,在StringBuilder中定義了一個字符數組m_ChunkChars,它保存StringBuilder所管理著的字(zi)符串中的字(zi)符。

  經過對StringBuilder默認構造方法的分析,系統默認初始化m_ChunkChars的長度為16(0x10),當新追加進來的字符串長度與舊有字符串長度之和大于該字符數組容量時,新創建字符數組的容量會增加到2n+1(假如當前字符數組容量為2n)。

  此外,StringBuilder內部還有一個同為StringBuilder類型的m_ChunkPrevious,它(ta)是內部的一(yi)個(ge)StringBuilder對(dui)象(xiang),前面提(ti)到當(dang)追(zhui)加的字(zi)(zi)符串長度(du)和舊字(zi)(zi)符串長度(du)之合(he)大(da)于字(zi)(zi)符數組m_ChunkChars的最大(da)容量(liang)時,會(hui)根據當(dang)前的(this)StringBuilder創(chuang)建一(yi)個(ge)新的StringBuilder對(dui)象(xiang),將m_ChunkPrevious指向新創(chuang)建的StringBuilder對(dui)象(xiang)。

  下(xia)面是(shi)StringBuilder中(zhong)實現擴容的核心代碼:

private void ExpandByABlock(int minBlockCharCount)
{
    ......
    int num = Math.Max(minBlockCharCount, Math.Min(this.Length, 0x1f40));
    this.m_ChunkPrevious = new StringBuilder(this);
    this.m_ChunkOffset += this.m_ChunkLength;
    this.m_ChunkLength = 0;
    ......
    this.m_ChunkChars = new char[num];
}

  可以看出,初始(shi)化m_ChunkPrevious在前(qian),創建新(xin)的(de)字符(fu)數組m_ChunkChars在后,最后才是(shi)復(fu)制(zhi)字符(fu)到數組m_ChunkChars中(更新(xin)當前(qian)的(de)m_ChunkChars)。歸根結底,StringBuilder是(shi)在內部以字符(fu)數組m_ChunkChars為基礎維護(hu)一(yi)個鏈(lian)表(biao)(biao)m_ChunkPrevious,該(gai)鏈(lian)表(biao)(biao)如下(xia)圖(tu)所示:

  在最終的ToString方(fang)法(fa)中(zhong),當前的StringBuilder對象(xiang)會根(gen)據這個鏈(lian)表以及記錄的長(chang)度和(he)偏移變(bian)量去生成(cheng)最終的一個String對象(xiang)實例,StringBuilder的內(nei)部實現中(zhong)使用了(le)一些指針操作,其(qi)內(nei)部原理(li)有興趣的園友可以自己去通過反編譯工具查看源代碼。

1.2 String和Byte[]對象之間如何相互轉換?

  在實際開發中,經常會對數據進行處理,不可避免地會遇到字符串和字節數組相互轉換的需求。字符串和字節數組的轉換,事實上是代表了現實世界信息和數字世界信息之間的轉換,要(yao)了解(jie)其(qi)中(zhong)的(de)機制,需要(yao)先對比(bi)特、直接以及編碼這三個(ge)概念有所了解(jie)。

  (1)比特:bit是一個位,計算機內物理保存的(de)最基(ji)本單元,一個bit就是一個二進制(zhi)位;

  (2)字節:byte由8個bit構成,其值可以由一個0~255的整數表示(shi);

  (3)編碼:編碼是數字信息和現實信息的轉換機制,一種(zhong)編碼(ma)通常就定義了(le)一種(zhong)字符集和轉換的原則,常用的編碼(ma)方式(shi)包括UTF8、GB2312、Unicode等(deng)。

  下圖直觀地展(zhan)示了比特、字(zi)節、編(bian)碼和字(zi)符(fu)串的關系:

  從上圖可以看出,字節數組和字符串的轉換必然涉及到某種編碼方式,不同的編碼方式由不同的轉換結果。在C#中,可以使用System.Text.Encoding來管理常用的編碼。

  下(xia)面的代碼展示了如(ru)何在(zai)字節(jie)數(shu)組和字符串之(zhi)間進行轉換(分別使用UTF8、GB2312以及Unicode三種編(bian)碼方式):

    class Program
    {
        static void Main(string[] args)
        {
            string s = "我是(shi)字(zi)符串,I am a string!";
            // 字(zi)節數組 -> 字(zi)符串
            Byte[] utf8 = StringToByte(s, Encoding.UTF8);
            Byte[] gb2312 = StringToByte(s, Encoding.GetEncoding("GB2312"));
            Byte[] unicode = StringToByte(s, Encoding.Unicode);

            Console.WriteLine(utf8.Length);
            Console.WriteLine(gb2312.Length);
            Console.WriteLine(unicode.Length);
            // 字(zi)符串 -> 字(zi)符數組(zu)
            Console.WriteLine(ByteToString(utf8, Encoding.UTF8));
            Console.WriteLine(ByteToString(gb2312, Encoding.GetEncoding("GB2312")));
            Console.WriteLine(ByteToString(unicode, Encoding.Unicode));

            Console.ReadKey();
        }

        // 字符串 -> 字節數(shu)組
        static Byte[] StringToByte(string str, Encoding encoding)
        {
            if (string.IsNullOrEmpty(str))
            {
                return null;
            }
            return encoding.GetBytes(str);
        }

        // 字(zi)節數組 -> 字(zi)符(fu)串(chuan)
        static string ByteToString(Byte[] bytes, Encoding encoding)
        {
            if (bytes == null || bytes.Length <= 0)
            {
                return string.Empty;
            }

            return encoding.GetString(bytes);
        }
    }
View Code

  上(shang)述代碼的運行(xing)結(jie)果如下(xia)圖所示:

  我們也可以從上圖中看出,不同的編碼方式產生的字節數組的長度各不相同

1.3 BASE64編碼的作用以及C#中對其的支持

  和傳統的編碼不同,BASE64編碼的設計致力于混淆那些8位字節的數據流(解決網絡傳輸中的明碼問題),在網絡傳輸、郵件等系統中被廣泛應用。需要明確的是:BASE64不屬于加密機制,但它卻是把明碼變成了一種很難識別的形式

  BASE64的算法如下:

BASE64把(ba)所(suo)有(you)的位(wei)分(fen)開,并且重(zhong)新組(zu)合(he)成字(zi)節(jie)(jie)(jie),新的字(zi)節(jie)(jie)(jie)只(zhi)包(bao)含(han)6位(wei),最后在每(mei)個(ge)(ge)(ge)(ge)字(zi)節(jie)(jie)(jie)前添加(jia)兩個(ge)(ge)(ge)(ge)0,組(zu)成了新的字(zi)節(jie)(jie)(jie)數(shu)組(zu)。例如:一個(ge)(ge)(ge)(ge)字(zi)節(jie)(jie)(jie)數(shu)組(zu)只(zhi)包(bao)含(han)三個(ge)(ge)(ge)(ge)字(zi)節(jie)(jie)(jie)(每(mei)個(ge)(ge)(ge)(ge)字(zi)節(jie)(jie)(jie)又有(you)8位(wei)比特),對其(qi)進行BASE64編(bian)碼時會將其(qi)分(fen)配到4個(ge)(ge)(ge)(ge)新的字(zi)節(jie)(jie)(jie)中(zhong)(zhong)(為什(shen)么是4個(ge)(ge)(ge)(ge)呢?計算3*8/6=4),其(qi)中(zhong)(zhong)每(mei)個(ge)(ge)(ge)(ge)字(zi)節(jie)(jie)(jie)只(zhi)填充(chong)低6位(wei),最后把(ba)高2位(wei)置為零。

  下圖清晰地展示了上(shang)面所講到的BASE64的算法(fa)示例(li):

  在.NET中(zhong),BASE64編碼的應用也(ye)很多,例如(ru)(ru)在ASP.NET WebForm中(zhong),默認為我(wo)們生成了一個ViewState來(lai)保持狀態,如(ru)(ru)下圖所示(shi):

viewstate

  這里的(de)ViewState其實就是服務器在返回給(gei)瀏覽器前進行(xing)了(le)一次BASE64編碼(ma),我們可(ke)以通過一些(xie)解(jie)碼(ma)工具進行(xing)反(fan)BASE64編碼(ma)查看其中的(de)奧秘:

Decoder

  那么,問題來了?在.NET中開發中,怎樣來進行BASE64的編碼和解碼呢,.NET基類庫中提供了一個Convert類,其中有兩個靜態方法提供了BASE64的編碼和解碼,但要注意的是:Convert類型在轉換失敗時會直接拋出異常,我們需要(yao)在開發中注意對潛在異常的處理(比如使用(yong)is或(huo)as來(lai)進行高效的類(lei)型轉換)。下面的代碼展示了其用(yong)法:

    class Program
    {
        static void Main(string[] args)
        {
            string test = "abcde ";
            // 生成UTF8字節數組(zu)
            byte[] bytes = Encoding.UTF8.GetBytes(test);
            // 轉換成Base64字符串
            string base64 = BytesToBase64(bytes);
            Console.WriteLine(base64);
            // 轉換(huan)回UTF8字(zi)節數組(zu)
            bytes = Base64ToBytes(base64);
            Console.WriteLine(Encoding.UTF8.GetString(bytes));

            Console.ReadKey();
        }

        // Bytes to Base64
        static string BytesToBase64(byte[] bytes)
        {
            try
            {
                return Convert.ToBase64String(bytes);
            }
            catch
            {
                return null;
            }
        }

        // Base64 to Bytes
        static Byte[] Base64ToBytes(string base64)
        {
            try
            {
                return Convert.FromBase64String(base64);
            }
            catch
            {
                return null;
            }
        }
    }
View Code

  上面代碼(ma)的執行結果如下(xia)圖所示:

  

1.4 簡述SecureString安全字符串的特點和用法

  也許很多人都是第一次知道還有SecureString這樣一個類型,我也不例外。SecureString并不是一個常用的類型,但在一些擁有特殊需求的額場合,它就會有很大的作用。顧名思義,SecureString意為安全的字符串,它被設計用來保存一些機密的字符串,完成傳統字符串所不能做到的工作

  (1)傳統字符串以明碼的形式被分配在內存中,一個簡單的內存讀寫軟件就可以輕易地捕獲這些字符串,而在這某些機密系統中是不被允許的。也許我們會覺得對字符串加密就可以解決類似問題,But,事實總是殘酷的,對字符串加密時字符串已經以明碼方式駐留在內存中很久了!對于該問題唯一的解決辦法就是在字符串的獲得過程中直接進行加密,SecureString的設計初衷就是解決該類問題

  (2)為了保證安全性,SecureString是被分配在非托管內存上的(而普通String是被分配在托管內存中的),并且SecureString的對象從分配的一開始就以加密的形式存在,我們所有對于SecureString的操作(無論是增刪查改)都是逐字符進行的

逐字符機制:在進行這些操作時,駐留在非托管內存中的字符串就會被解密,然后進行具體操作,最后再進行加密。不可否認的是,在具體操作的過程中有小段時間字符串是處于明碼狀態的,但逐字符(fu)的機(ji)制(zhi)讓這段時間維持在(zai)非常短的區間內,以(yi)保證(zheng)破解程(cheng)序(xu)很難有(you)機(ji)會讀取明碼的字符(fu)串。

  (3)為了保證資源釋放,SecureString實現了標準的Dispose模式(Finalize+Dispose雙管齊(qi)下,因為上(shang)面提到(dao)(dao)它是被(bei)分配到(dao)(dao)非(fei)托管內存中的),保證每(mei)個對(dui)象在作用(yong)域(yu)退出后都(dou)可(ke)以被(bei)釋放(fang)掉。

內存釋放方式:將其對象(xiang)內存全(quan)部置(zhi)為0,而不(bu)(bu)是僅僅告訴(su)CLR這(zhe)一(yi)塊內存可以分配,當然這(zhe)樣做仍然是為了確保(bao)安全(quan)。熟(shu)悉C/C++的朋(peng)友可能就會很熟(shu)悉,這(zhe)不(bu)(bu)就是 memset 函數干的事(shi)情嘛!下面這(zhe)段C代碼便(bian)使(shi)用了memset函數將內存區域(yu)置(zhi)為0:

    // 下(xia)面申請的(de)20個(ge)字節(jie)的(de)內(nei)存(cun)有可能(neng)被別(bie)人用過
    char chs[20];
    // memset內存初始化:memset(void *,要(yao)(yao)填充的數(shu)據,要(yao)(yao)填充的字(zi)節個數(shu))
    memset(chs,0,sizeof(chs));

  看完了SecureString的(de)原理(li),現在我們通過(guo)下面(mian)的(de)代碼來(lai)熟悉一下在.NET中(zhong)的(de)基本用法:

using System;
using System.Runtime.InteropServices;
using System.Security;

namespace UseSecureString
{
    class Program
    {
        static void Main(string[] args)
        {
            // 使用using語句(ju)保證Dispose方法被及時調用
            using (SecureString ss = new SecureString())
            {
                // 只能逐字符地操作SecureString對(dui)象(xiang)
                ss.AppendChar('e');
                ss.AppendChar('i');
                ss.AppendChar('s');
                ss.AppendChar('o');
                ss.AppendChar('n');
                ss.InsertAt(1, 'd');
                // 打印SecureStrign對(dui)象(xiang)
                PrintSecureString(ss);
            }

            Console.ReadKey();
        }

        // 打印SecureString對象
        public unsafe static void PrintSecureString(SecureString ss)
        {
            char* buffer = null;

            try
            {
                // 只(zhi)能逐(zhu)字(zi)符(fu)地訪問(wen)SecureString對象(xiang)
                buffer = (char*)Marshal.SecureStringToCoTaskMemUnicode(ss);
                for (int i = 0; *(buffer + i) != '\0'; i++)
                {
                    Console.Write(*(buffer + i));
                }
            }
            finally
            {
                // 釋放(fang)內(nei)存(cun)對象
                if (buffer != null)
                {
                    Marshal.ZeroFreeCoTaskMemUnicode((System.IntPtr)buffer);
                }
            }
        }
    }
}
View Code

  其運行(xing)顯(xian)示的結(jie)果(guo)很簡(jian)單:

  

  這里需要注意的是:為了顯示SecureString的內容,程序需要訪問非托管內存,因此會用到指針,而要在C#使用指針,則需要使用unsafe關鍵字(前提是你在項目屬性中勾選了允許不安全代碼,對你(ni)沒(mei)看錯,指針(zhen)在(zai)C#可以(yi)使用,但是被認(ren)為是不安全(quan)(quan)的!)。此(ci)外(wai),程序中(zhong)使用了Marshal.SecureStringToCoTaskMemUnicode方法來把安全(quan)(quan)字符串解密(mi)到非(fei)托管(guan)內存中(zhong),最后(hou)就是就是我們不要忘記在(zai)使用非(fei)托管(guan)資源時需要確保及(ji)時被釋放。

1.5 簡述字符串駐留池機制

  字符串具有不可變性,程序中對于同一個字符串的大量修改或者多個引用賦值同一字符串在理論上會產生大量的臨時字符串對象,這會極大地降低系統的性能。對于前者,可以使用StringBuilder類型解決,而后者,.NET則提供了另一種不透明的機制來優化,這就是傳說中的字符串駐留池機制。

  使用了字符串駐留池機制之后,當CLR啟動時,會在內部創建一個容器,該容器內部維持了一個類似于key-value對的數據結構,其中key是字符串的內容,而value則是字符串在托管堆上的引用(也可以理解為指針或地址)。當一個新的字符串對象需要分配時,CLR首先監測內部容器中是否已經存在該字符串對象,如果已經包含則直接返回已經存在的字符串對象引用;如果不存在,則新分配一個字符串對象,同時把其添加到內部容器中取。But,這里有一個例外,就是當程序員用new關鍵字顯示地申明新分配一個字符串對象時,該機制將不會起作用

  從上(shang)面的(de)描述(shu)中,我們可(ke)以看到(dao)字符(fu)(fu)串(chuan)(chuan)駐留池的(de)本質是一個(ge)緩(huan)存(cun),內部維(wei)持了一個(ge)鍵為字符(fu)(fu)串(chuan)(chuan)內容,值(zhi)為該字符(fu)(fu)串(chuan)(chuan)在堆中的(de)引用地(di)址的(de)鍵值(zhi)對數(shu)據結構(gou)。我們可(ke)以通過下面一段代碼來加(jia)深(shen)對于字符(fu)(fu)串(chuan)(chuan)駐留池的(de)理(li)解:

    class Program
    {
        static void Main(string[] args)
        {
            // 01.兩個(ge)字(zi)符串對象,理論上引(yin)用(yong)應該不相等
            // 但是由(you)于字符(fu)串池機制(zhi),二者指向了同一對象
            string a = "abcde";
            string b = "abcde";
            Console.WriteLine(object.ReferenceEquals(a, b));
            // 02.由于編譯器的優化,所以下(xia)面這個c仍(reng)然指向(xiang)了同一(yi)引(yin)用地址
            string c = "a" + "bc" + "de";
            Console.WriteLine(object.ReferenceEquals(a, c));
            // 03.顯示(shi)地使(shi)用new來分(fen)配內存(cun),這時候字符(fu)串池不(bu)起(qi)作用
            char[] arr = { 'a', 'b', 'c', 'd', 'e' };
            string d = new string(arr);
            Console.WriteLine(object.ReferenceEquals(a, d));

            Console.ReadKey();
        }
    }
View Code

  在上述代碼(ma)中(zhong),由于字符串駐留(liu)池機(ji)制(zhi)的使用(yong)(yong)(yong),變量a、b、c都(dou)指向(xiang)了同一個(ge)字符串實例(li)對象,而d則(ze)使用(yong)(yong)(yong)了new關(guan)鍵(jian)字顯(xian)示申(shen)明(ming),因此字符串駐留(liu)池并沒有對其起作用(yong)(yong)(yong),其運(yun)行結果如下(xia)圖(tu)所示:

  

  字符串(chuan)(chuan)駐(zhu)(zhu)留池的設計本意是為(wei)了(le)改善程序(xu)的性(xing)能,因(yin)此(ci)在C#中默認是打開了(le)字符串(chuan)(chuan)駐(zhu)(zhu)留池機制,But,.NET也為(wei)我(wo)們提供了(le)字符串(chuan)(chuan)駐(zhu)(zhu)留池的開關接口,如果程序(xu)集標記了(le)一個(ge)System.Runtime.CompilerServices.CompilationRelaxationsAttribute特性(xing),并且指定(ding)了(le)一個(ge)System.Runtime.CompilerServices.CompilationRelaxations.NoStringInterning標志,那(nei)么CLR不會采用(yong)字符串(chuan)(chuan)駐(zhu)(zhu)留池機制,其代碼(ma)聲明(ming)如下(xia)所示,但是我(wo)添加(jia)后一直沒(mei)有嘗試成功:

[assembly: System.Runtime.CompilerServices.CompilationRelaxations(System.Runtime.CompilerServices.CompilationRelaxations.NoStringInterning)]  

二、常用集合和泛型

2.1 int[]是值類型還是引用類型?

  在.NET中的數組類型和C++中區別很大,.NET中無論是存儲值類型對象的數組還是存儲引用類型的數組,其本身都是引用類型,其內存也都是分配在堆上的。它們的共同特征在于:所有的數組類型都繼承自System.Array,而System.Array又實現了多個接口,并且直接繼承自System.Object。不(bu)同之(zhi)處則在(zai)(zai)于(yu)存儲值類(lei)型對象(xiang)的(de)數組(zu)所有的(de)值都(dou)已(yi)經包含在(zai)(zai)數組(zu)內(nei),而存儲引用類(lei)型對象(xiang)的(de)數組(zu),其值則是(shi)一個引用,指向位于(yu)托管堆(dui)中的(de)實(shi)例對象(xiang)。

  下圖直觀(guan)地展示了二者內存分(fen)配的差(cha)別(假設object[]中存儲(chu)都(dou)是DateTime類型的對象(xiang)實例(li)):

  在.NET中(zhong)CLR會檢測所有(you)對數(shu)組(zu)的訪問,任何(he)視圖訪問數(shu)組(zu)邊界以外的代碼都(dou)會產(chan)生(sheng)一個IndexOutOfRangeException異常。

2.2 數組之間如何進行轉換?

  數組類型(xing)的(de)轉(zhuan)換需(xu)要遵循(xun)以下(xia)兩個原則:

  (1)包含值類型的數組不能被隱式轉換成其他任何類型

  (2)兩個數組類型能夠相互轉換的一個前提是兩者維數相同

  我們可以通(tong)過以下代碼來看看數組類型轉換(huan)的(de)機制(zhi):

    // 編(bian)譯成功
    string[] sz = { "a", "a", "a" };
    object[] oz = sz;
    // 編譯失敗(bai),值類(lei)型的數(shu)組不能(neng)被轉換
    int[] sz2 = { 1, 2, 3 };
    object[] oz2 = sz;
    // 編譯失敗(bai),兩者(zhe)維數不同
    string[,] sz3 = { { "a", "b" }, { "a", "c" } };
    object[] oz3 = sz3;

  除了類型上的轉換,我們平時還可能會遇到內容轉換的需求。例如,在一系列的用戶界面操作之后,系統的后臺可能會得到一個DateTime的數組,而現在的任務則是將它們存儲到數據庫中,而數據庫訪問層提供的接口只接受String[]參數,這時我們要做的就是把DateTime[]從內容上轉換為String[]對象。當然,慣常做法是遍歷整個源數組,逐一地轉換每個對象并且將其放入一個目標數組類型容器中,最后再生成目標數組。But,這里我們推薦使用Array.ConvertAll方法,它提供了一個簡便的轉換數組間內容的接口,我(wo)們(men)只需指(zhi)定源數(shu)組的類型(xing)、對象數(shu)組的類型(xing)和具(ju)體(ti)的轉換算法(fa),該方(fang)法(fa)就能(neng)高效地完(wan)成轉換工作。

  下面的代碼清楚地(di)展示了普通的數(shu)組(zu)內(nei)(nei)容轉換方式和使用Array.ConvertAll的數(shu)組(zu)內(nei)(nei)容轉換方式的區(qu)別(bie):

    class Program
    {
        static void Main(string[] args)
        {
            String[] times ={"2008-1-1",
                            "2008-1-2",
                            "2008-1-3"};

            // 使用不(bu)同的方法轉換(huan)
            DateTime[] result1 = OneByOne(times);
            DateTime[] result2 = ConvertAll(times);

            // 結果是(shi)相同的
            Console.WriteLine("手動逐(zhu)個轉換的方法:");
            foreach (DateTime item in result1)
            {
                Console.WriteLine(item.ToString("yyyy-MM-dd"));
            }
            Console.WriteLine("使用Array.Convert方法:");
            foreach (DateTime item2 in result2)
            {
                Console.WriteLine(item2.ToString("yyyy-MM-dd"));
            }

            Console.ReadKey();
        }

        // 逐(zhu)個(ge)手動轉換(huan)
        private static DateTime[] OneByOne(String[] times)
        {
            List<DateTime> result = new List<DateTime>();
            foreach (String item in times)
            {
                result.Add(DateTime.Parse(item));
            }
            return result.ToArray();
        }

        // 使用Array.ConertAll方(fang)法
        private static DateTime[] ConvertAll(String[] times)
        {
            return Array.ConvertAll(times,
                new Converter<String, DateTime>
                (DateTimeToString));
        }

        private static DateTime DateTimeToString(String time)
        {
            return DateTime.Parse(time);
        }
    }
View Code

  從上述代碼可以(yi)看(kan)(kan)出,二(er)者實現了相同的功(gong)能(neng),但是Array.ConvertAll不(bu)(bu)需要我(wo)們手動地遍(bian)歷數組,也(ye)不(bu)(bu)需要生成一(yi)(yi)(yi)個臨時的容器對象,更(geng)突出的優勢是它(ta)可以(yi)接受一(yi)(yi)(yi)個動態(tai)的算法(fa)作(zuo)為具(ju)體的轉(zhuan)換(huan)邏輯。當然,明(ming)眼(yan)人一(yi)(yi)(yi)看(kan)(kan)就知道,它(ta)是以(yi)一(yi)(yi)(yi)個委托的形(xing)式作(zuo)為參數傳入,這樣(yang)的機制(zhi)保證了Array.ConvertAll具(ju)有較高的靈活性。

2.3 簡述泛型的基本原理

  泛型的語法和概念類似于C++中的template(模板),它是.NET 2.0中推出的眾多特性中最為重要的一個,方便我們設計更加通用的類型,也避免了容器操作中的裝箱和拆箱操作

  假(jia)如我(wo)(wo)們要實(shi)(shi)現(xian)一個排序(xu)算法(fa),要求能(neng)夠針(zhen)對(dui)各種(zhong)類型進行(xing)排序(xu)。按照以前的做(zuo)法(fa),我(wo)(wo)們需要對(dui)int、double、float等類型都實(shi)(shi)現(xian)一次,但是我(wo)(wo)們發現(xian)除了數據類型,其他的處理邏輯(ji)完(wan)全一致(zhi)。這時,我(wo)(wo)們便可以考慮(lv)使用泛(fan)型來進行(xing)實(shi)(shi)現(xian):

    public static class SortHelper<T> where T : IComparable
    {
        public static void BubbleSort(T[] array)
        {
            int length = array.Length;
            for (int i = 0; i <= length - 2; i++)
            {
                for (int j = length - 1; j >= 1; j--)
                {
                    // 對(dui)兩(liang)個元(yuan)素進行交換            
                    if (array[j].CompareTo(array[j - 1]) < 0)
                    {
                        T temp = array[j];
                        array[j] = array[j - 1];
                        array[j - 1] = temp;
                    }
                }
            }
        }
    }

Tips:Microsoft在(zai)產品文檔中(zhong)建議所(suo)有的泛型(xing)參(can)數名稱都(dou)以T開頭(tou),作(zuo)為一個(ge)中(zhong)編碼(ma)的通用規(gui)(gui)范(fan),建議大家都(dou)能遵守這樣的規(gui)(gui)范(fan),類似(si)的規(gui)(gui)范(fan)還有所(suo)有的接口(kou)都(dou)以I開頭(tou)。

  泛型類型和普通類型有一定的區別,通常泛型類型被稱為開放式類型,.NET中規定開(kai)放(fang)式(shi)(shi)類(lei)(lei)型(xing)(xing)(xing)(xing)不能實例(li)(li)化,這(zhe)樣也就確保了(le)開(kai)放(fang)式(shi)(shi)類(lei)(lei)型(xing)(xing)(xing)(xing)的(de)(de)(de)泛型(xing)(xing)(xing)(xing)參數在(zai)被(bei)指定前,不會被(bei)實例(li)(li)化成(cheng)(cheng)任(ren)何對象(事實上,.NET也沒(mei)有(you)(you)辦法確定到底(di)要分配多(duo)少內存給開(kai)放(fang)式(shi)(shi)類(lei)(lei)型(xing)(xing)(xing)(xing))。為(wei)開(kai)放(fang)式(shi)(shi)的(de)(de)(de)類(lei)(lei)型(xing)(xing)(xing)(xing)提供(gong)泛型(xing)(xing)(xing)(xing)的(de)(de)(de)實例(li)(li)導致了(le)一個新的(de)(de)(de)封閉類(lei)(lei)型(xing)(xing)(xing)(xing)的(de)(de)(de)生成(cheng)(cheng),但這(zhe)并不代(dai)表新的(de)(de)(de)封閉類(lei)(lei)型(xing)(xing)(xing)(xing)和(he)開(kai)放(fang)類(lei)(lei)型(xing)(xing)(xing)(xing)有(you)(you)任(ren)何繼承關(guan)(guan)系(xi),它(ta)們在(zai)類(lei)(lei)結構圖上是處于(yu)同一層(ceng)次,并且兩者之間沒(mei)有(you)(you)任(ren)何關(guan)(guan)系(xi)。下圖展示了(le)這(zhe)一概念:

  此外,在.NET中的System.Collections.Generic命名空間下提供了諸如List<T>、Dictionary<T>、LinkedList<T>等泛型數據結構,并且在System.Array中定義了一些靜態的泛型方法,我們應該在編碼實踐時充分使用這些泛型容器,以提高我們的開發和系統的運行效率

2.4 泛型的主要約束和次要約束是什么?

  當一個(ge)泛型(xing)參數沒有(you)(you)任(ren)何約(yue)(yue)束(shu)時,它可以(yi)(yi)進(jin)(jin)行(xing)的(de)操作和(he)運算是非常有(you)(you)限的(de),因為不能對實(shi)參進(jin)(jin)行(xing)任(ren)何類(lei)型(xing)上的(de)保證,這時候(hou)就需要(yao)(yao)用到泛型(xing)約(yue)(yue)束(shu)。泛型(xing)的(de)約(yue)(yue)束(shu)分為:主要(yao)(yao)約(yue)(yue)束(shu)和(he)次要(yao)(yao)約(yue)(yue)束(shu),它們都使(shi)實(shi)參必(bi)須滿足一定的(de)規范(fan),C#編譯(yi)器(qi)在編譯(yi)的(de)過(guo)程(cheng)中可以(yi)(yi)根據約(yue)(yue)束(shu)來檢查所有(you)(you)泛型(xing)類(lei)型(xing)的(de)實(shi)參并(bing)確保其(qi)滿足約(yue)(yue)束(shu)條(tiao)件。

  (1)主要約束

  一(yi)個(ge)(ge)泛型參(can)數(shu)至多擁有一(yi)個(ge)(ge)主要(yao)約(yue)束,主要(yao)約(yue)束可以是(shi)一(yi)個(ge)(ge)引(yin)用類(lei)(lei)型、class或者struct。如果指定一(yi)個(ge)(ge)引(yin)用類(lei)(lei)型(class),那(nei)么實(shi)參(can)必須(xu)是(shi)該類(lei)(lei)型或者該類(lei)(lei)型的(de)派生類(lei)(lei)型。相反,struct則規定了實(shi)參(can)必須(xu)是(shi)一(yi)個(ge)(ge)值類(lei)(lei)型。下面的(de)代碼展示了泛型參(can)數(shu)主要(yao)約(yue)束:

    public class ClassT1<T> where T : Exception
    {
        private T myException;
        public ClassT1(T t)
        {
            myException = t;
        }
        public override string ToString()
        {
            // 主要約束(shu)保(bao)證了myException擁有source成員
            return myException.Source;
        }
    }

    public class ClassT2<T> where T : class
    {
        private T myT;
        public void Clear()
        {
            // T是引用類型,可以置null
            myT = null;
        }
    }

    public class ClassT3<T> where T : struct
    {
        private T myT;
        public override string ToString()
        {
            // T是(shi)值類型,不會(hui)發生NullReferenceException異(yi)常
            return myT.ToString();
        }
    }
View Code

  泛型(xing)參數有了(le)主(zhu)要(yao)約束后,也(ye)就(jiu)能夠在類型(xing)中對其進行(xing)一定的操作了(le)。

  (2)次要約束

  次(ci)(ci)(ci)要(yao)(yao)(yao)約(yue)(yue)束(shu)主(zhu)要(yao)(yao)(yao)是指實(shi)參實(shi)現的接(jie)口的限(xian)定。對于一(yi)個泛(fan)型,可以有(you)0到無限(xian)的次(ci)(ci)(ci)要(yao)(yao)(yao)約(yue)(yue)束(shu),次(ci)(ci)(ci)要(yao)(yao)(yao)約(yue)(yue)束(shu)規定了實(shi)參必須實(shi)現所有(you)的次(ci)(ci)(ci)要(yao)(yao)(yao)約(yue)(yue)束(shu)中規定的接(jie)口。次(ci)(ci)(ci)要(yao)(yao)(yao)約(yue)(yue)束(shu)與主(zhu)要(yao)(yao)(yao)約(yue)(yue)束(shu)的語法基(ji)本一(yi)致,區別僅在(zai)于提供的不是一(yi)個引(yin)用(yong)類(lei)型而是一(yi)個或多個接(jie)口。例如(ru)我們為上面代(dai)碼中的ClassT3增(zeng)加一(yi)個次(ci)(ci)(ci)要(yao)(yao)(yao)約(yue)(yue)束(shu):

    public class ClassT3<T> where T : struct, IComparable
    {
        ......      
    }
View Code

三、流和序列化

3.1 流的概念以及.NET中有哪些常見的流?

  流是一種針對字節流的操作,它類似于內存與文件之間的一個管道。在(zai)對一個文(wen)(wen)件(jian)進行(xing)處理時,本質上需要經過借助OS提供的API來進行(xing)打(da)開文(wen)(wen)件(jian),讀取文(wen)(wen)件(jian)中(zhong)的字節流,再關(guan)閉文(wen)(wen)件(jian)等操作(zuo)(zuo),其中(zhong)讀取文(wen)(wen)件(jian)的過程就(jiu)可以看作(zuo)(zuo)是字節流的一個過程。

  常見的流類型包括:文件流、終端操作流以及網絡Socket等,在.NET中,System.IO.Stream類(lei)型(xing)被設計為作為所(suo)有流(liu)(liu)類(lei)型(xing)的(de)(de)虛基類(lei),所(suo)有的(de)(de)常見流(liu)(liu)類(lei)型(xing)都(dou)繼承自(zi)System.IO.Stream類(lei)型(xing),當(dang)我們需要(yao)自(zi)定義一種流(liu)(liu)類(lei)型(xing)時,也應該直接或者(zhe)間接地繼承自(zi)Stream類(lei)型(xing)。下圖展示了在.NET中常見的(de)(de)流(liu)(liu)類(lei)型(xing)以及它們的(de)(de)類(lei)型(xing)結構:

  從上圖中可以發現,Stream類型繼承自MarshalByRefObject類型,這保證了流類型可以跨越(yue)應用程序(xu)域(yu)進行交互。所有常用的流類型(xing)(xing)都繼(ji)承自System.IO.Stream類型(xing)(xing),這保(bao)證(zheng)了(le)流類型(xing)(xing)的同一性,并(bing)且屏蔽(bi)了(le)底層的一些復(fu)雜操作,使(shi)用起來非常方便。

  下面的代碼(ma)中(zhong)展示了(le)如何(he)在.NET中(zhong)使用FileStream文件流(liu)進(jin)行(xing)簡(jian)單的文件讀寫操作:

    class Program
    {
        private const int bufferlength = 1024;

        static void Main(string[] args)
        {
            //創建一個文件,并寫入(ru)內容
            string filename = @"C:\TestStream.txt";
            string filecontent = GetTestString();

            try
            {
                if (File.Exists(filename))
                {
                    File.Delete(filename);
                }

                // 創(chuang)建文件(jian)并寫入內容
                using (FileStream fs = new FileStream(filename, FileMode.Create))
                {
                    Byte[] bytes = Encoding.UTF8.GetBytes(filecontent);
                    fs.Write(bytes, 0, bytes.Length);
                }

                // 讀取文件并打(da)印出來(lai)
                using (FileStream fs = new FileStream(filename, FileMode.Open))
                {
                    Byte[] bytes = new Byte[bufferlength];
                    UTF8Encoding encoding = new UTF8Encoding(true);
                    while (fs.Read(bytes, 0, bytes.Length) > 0)
                    {
                        Console.WriteLine(encoding.GetString(bytes));
                    }
                }
                // 循環(huan)分(fen)批(pi)讀取打印
                //using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
                //{
                //    Byte[] bytes = new Byte[bufferlength];
                //    int bytesRead;
                //    do
                //    {
                //        bytesRead = fs.Read(bytes, 0, bufferlength);
                //        Console.WriteLine(Encoding.UTF8.GetString(bytes, 0, bytesRead));
                //    } while (bytesRead > 0);
                //}
            }
            catch (IOException ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.ReadKey();
        }

        // 01.取(qu)得(de)測試(shi)數據
        static string GetTestString()
        {
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < 10; i++)
            {
                builder.Append("我是測試數據\r\n");
                builder.Append("我是長江(jiang)" + (i + 1) + "號(hao)\r\n");
            }
            return builder.ToString();
        }
    }
View Code

  上述(shu)代碼(ma)的執行結果如下圖所示:

      

  在實際開發中,我們經常會遇到需要傳遞一個比較大的文件,或者事先無法得知文件大小(Length屬性拋出異常),因此也就不能創建一個尺寸正好合適的Byte[]數組,此時只能分批讀取和寫入每次只讀取部分字節,直到文件尾。例如(ru)我們需要復(fu)制G盤(pan)中一個大小為4.4MB的(de)mp3文件到(dao)C盤(pan)中去,假(jia)設我們對大小超過2MB的(de)文件都采(cai)用(yong)分批讀取寫(xie)入機制,可以通過如(ru)下(xia)代碼實現(xian):

    class Program
    {
        private const int BufferSize = 10240; // 10 KB
        public static void Main(string[] args)
        {
            string fileName = @"G:\My Musics\BlueMoves.mp3"; // Source 4.4 MB
            string copyName = @"C:\BlueMoves-Copy.mp3"; // Destination 4.4 MB
            using (Stream source = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                using (Stream target = new FileStream(copyName, FileMode.Create, FileAccess.Write))
                {
                    byte[] buffer = new byte[BufferSize];
                    int bytesRead;
                    do
                    {
                        // 從(cong)源文件中讀取指定的10K長度(du)到緩存中
                        bytesRead = source.Read(buffer, 0, BufferSize);
                        // 從緩存中寫(xie)入(ru)已讀取到的長(chang)度(du)到目標文件中
                        target.Write(buffer, 0, bytesRead);
                    } while (bytesRead > 0);
                }
            }
            Console.ReadKey();
        }
    }
View Code

  上述代碼(ma)中(zhong),設置了緩(huan)存buffer大(da)小(xiao)為10K,即每次只(zhi)讀取10K的內容長度到buffer中(zhong),通過循環的多次讀寫和(he)寫入完成整個復(fu)制(zhi)操作(zuo)。

3.2 如何使用壓縮流?

  由于網絡帶寬的限制、硬盤內存空間的限制等原因,文件和數據的壓縮是我們經常會遇到的一個需求。因此,.NET中提供了對于壓縮和解壓的支持:GZipStream類型和DeflateStream類型,它們位于System.IO.Compression命名空間下,且都繼承于Stream類型(對文件壓縮的(de)本質其實(shi)是針對字節(jie)的(de)操作(zuo),也(ye)屬于一種流的(de)操作(zuo)),實(shi)現了基本一致的(de)功能。

  下面的(de)代碼展示了GZipStream的(de)使用方(fang)法,DeflateStream和GZipStream的(de)使用方(fang)法幾乎完(wan)全一(yi)致:

    class Program
    {
        // 緩存數組的長度
        private const int bufferSize = 1024;

        static void Main(string[] args)
        {
            string test = GetTestString();
            byte[] original = Encoding.UTF8.GetBytes(test);
            byte[] compressed = null;
            byte[] decompressed = null;
            Console.WriteLine("數據的原始長度是:{0}", original.LongLength);
            // 1.進(jin)行壓縮(suo)
            // 1.1 壓縮進(jin)入內存流
            using (MemoryStream target = new MemoryStream())
            {
                using (GZipStream gzs = new GZipStream(target, CompressionMode.Compress, true))
                {
                    // 1.2 將(jiang)數據寫入壓縮流
                    WriteAllBytes(gzs, original, bufferSize);
                }
                compressed = target.ToArray();
                Console.WriteLine("壓縮后(hou)的數據長(chang)度:{0}", compressed.LongLength);
            }
            // 2.進行解壓縮
            // 2.1 將解壓(ya)后的數據寫入(ru)內存流(liu)
            using (MemoryStream source = new MemoryStream(compressed))
            {
                using (GZipStream gzs = new GZipStream(source, CompressionMode.Decompress, true))
                {
                    // 2.2 從壓縮流中讀取所有數據
                    decompressed = ReadAllBytes(gzs, bufferSize);
                }
                Console.WriteLine("解壓后的數據(ju)長度:{0}", decompressed.LongLength);
                Console.WriteLine("解壓前后是否相等:{0}", test.Equals(Encoding.UTF8.GetString(decompressed)));
            }
            Console.ReadKey();
        }

        // 01.取得測(ce)試(shi)數據(ju)
        static string GetTestString()
        {
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < 10; i++)
            {
                builder.Append("我是(shi)測試數據\r\n");
                builder.Append("我(wo)是(shi)長江" + (i + 1) + "號\r\n");
            }
            return builder.ToString();
        }

        // 02.從一個(ge)流總讀取所有字節(jie)
        static Byte[] ReadAllBytes(Stream stream, int bufferlength)
        {
            Byte[] buffer = new Byte[bufferlength];
            List<Byte> result = new List<Byte>();
            int read;
            while ((read = stream.Read(buffer, 0, bufferlength)) > 0)
            {
                if (read < bufferlength)
                {
                    Byte[] temp = new Byte[read];
                    Array.Copy(buffer, temp, read);
                    result.AddRange(temp);
                }
                else
                {
                    result.AddRange(buffer);
                }
            }
            return result.ToArray();
        }

        // 03.把字節寫(xie)入一個流(liu)中(zhong)
        static void WriteAllBytes(Stream stream, Byte[] data, int bufferlength)
        {
            Byte[] buffer = new Byte[bufferlength];
            for (long i = 0; i < data.LongLength; i += bufferlength)
            {
                int length = bufferlength;
                if (i + bufferlength > data.LongLength)
                {
                    length = (int)(data.LongLength - i);
                }
                Array.Copy(data, i, buffer, 0, length);
                stream.Write(buffer, 0, length);
            }
        }
    }
View Code

  上述代碼的運行結果如下圖(tu)所示:

  

  需要注意的是:使用 GZipStream 類壓縮大于 4 GB 的文件時將會引發異常

  通過GZipStream的構造方法可以看出,它是一個典型的Decorator裝飾者模式的(de)(de)(de)應(ying)用(yong),所謂裝飾者(zhe)模式,就(jiu)是(shi)動態(tai)地(di)給一個(ge)對(dui)象(xiang)添加(jia)一些額外的(de)(de)(de)職責。對(dui)于增(zeng)加(jia)新(xin)功(gong)能這個(ge)方面(mian),裝飾者(zhe)模式比新(xin)增(zeng)一個(ge)之(zhi)類更為靈活。就(jiu)拿上面(mian)代碼中的(de)(de)(de)GZipStream來(lai)說,它擴展的(de)(de)(de)是(shi)MemoryStream,為Write方法增(zeng)加(jia)了壓縮的(de)(de)(de)功(gong)能,從而實現了壓縮的(de)(de)(de)應(ying)用(yong)。

擴展:許多資料表明(ming).NET提供的(de)(de)GZipStream和DeflateStream類型的(de)(de)壓(ya)(ya)縮算(suan)法(fa)并(bing)不出色,也不能調整壓(ya)(ya)縮率,有些第三方的(de)(de)組件例如SharpZipLib實現(xian)了更(geng)高效(xiao)的(de)(de)壓(ya)(ya)縮和解(jie)壓(ya)(ya)算(suan)法(fa),我(wo)們可以(yi)在nuget中(zhong)為項目添(tian)加(jia)該組件。

3.3 Serializable特性有什么作用?

  通過上面的流類型可以方便地操作各種字節流,但是如何把現有的實例對象轉換為方便傳輸的字節流,就需要使用序列化技術。對象實例的序列化,是指將實例對象轉換為可方便存儲、傳輸和交互的流。在.NET中,通過Serializable特性(xing)提供了(le)序(xu)列化對象實例的機制,當一個(ge)類型被(bei)申明為(wei)Serializable后(hou),它就能被(bei)諸如BinaryFormatter等實現了(le)IFormatter接(jie)口的類型進行序(xu)列化和(he)反序(xu)列化。

    [Serializable]
    public class Person
    {
        ......
    }

  但是,在實際開發中我們會遇到對于一些特殊的不希望被序列化的成員,這時我們可以為某些成員添加NonSerialized特性。例如,有(you)如下代(dai)碼所示的一(yi)個Person類,其(qi)中number代(dai)表(biao)學號,name代(dai)表(biao)姓名,我們不希望name被序列化(hua),于是可以為name添加NonSerialized特性:

    class Program
    {
        static void Main(string[] args)
        {
            Person obj = new Person(26, "Edison Chou");
            Console.WriteLine("初始狀(zhuang)態(tai):");
            Console.WriteLine(obj);

            // 序列(lie)化對象
            byte[] data = Serialize(obj);
            // 反序列化對(dui)象
            Person newObj = DeSerialize(data);

            Console.WriteLine("經過序(xu)列化(hua)和反(fan)序(xu)列化(hua)后(hou):");
            Console.WriteLine(newObj);

            Console.ReadKey();
        }

        // 序列化(hua)對象
        static byte[] Serialize(Person p)
        {
            // 使(shi)用二進制序列化
            IFormatter formatter = new BinaryFormatter();
            using (MemoryStream ms = new MemoryStream())
            {
                formatter.Serialize(ms, p);
                return ms.ToArray();
            }
        }

        // 反序列(lie)化對象
        static Person DeSerialize(byte[] data)
        {
            // 使用二進制反序列化
            IFormatter formatter = new BinaryFormatter();
            using (MemoryStream ms = new MemoryStream(data))
            {
                Person p = formatter.Deserialize(ms) as Person;
                return p;
            }
        }
    }
View Code

  上述(shu)代(dai)碼(ma)的運行結果如(ru)下(xia)圖所示:

  

注意:當一個(ge)基類使(shi)用(yong)了Serializable特性后,并不意(yi)味(wei)著(zhu)其(qi)所(suo)有子類都能(neng)被序(xu)列(lie)化。事實(shi)上,我們必須(xu)為每(mei)個(ge)子類都添加(jia)Serializable特性才能(neng)保(bao)證其(qi)能(neng)被正確地(di)序(xu)列(lie)化。

3.4 .NET提供了哪幾種可進行序列化操作的類型?

  我們已經理解了如何把一個類型聲明為可序列化的類型,但是萬里長征只走了第一步,具體完成序列化和反序列化的操作還需要一個執行這些操作的類型。為了序列化具體實例到某種專用的格式,.NET中提供了三種對象序列格式化類型:BinaryFormatterSoapFormatterXmlSerializer

  (1)BinaryFormatter

  顧名思義,BinaryFormatter可用于將可序列化的對象序列化成二進制的字節流,在前面Serializable特(te)性(xing)的代碼示(shi)例中已經展(zhan)示(shi)過,這里不(bu)再重復(fu)展(zhan)示(shi)。

  (2)SoapFormatter

  SoapFormatter致力于將可序列化(hua)的(de)類型序列化(hua)成符(fu)合SOAP規范的(de)XML文檔以(yi)供使用。在.NET中,要使用SoapFormatter需要先添加(jia)對于SoapFormatter的(de)引(yin)用:

using System.Runtime.Serialization.Formatters.Soap;

Tips:SOAP是一種位于(yu)應用層的網絡協議,它基于(yu)XML,并且是Web Service的基本協議。

  (3)XmlSerializer

  XmlSerializer并不僅僅針對那些標記了Serializable特性的類型,更為需要注意的是,Serializable和NonSerialized特性在XmlSerializer類型對象的操作中完全不起作用,取而代之的是XmlIgnore屬性(xing)。XmlSerializer可以對沒有標記(ji)Serializable特性(xing)的(de)類型對象進行序列化(hua),但是(shi)它仍(reng)然有一(yi)定的(de)限制:

  ① 使用XmlSerializer序列化的對象必須顯示地擁有一個無參數的公共構造方法

  因此,我們(men)需要(yao)修改(gai)前(qian)面(mian)代(dai)碼(ma)示例中的Person類(lei),添加(jia)一個無(wu)參數(shu)的公共構造方(fang)法:

    [Serializable]
    public class Person
    {
        ......
        public Person()
        {
        }
        ......
    }

  ② XmlSerializer只能序列化公共成員變量

  因此,Person類(lei)中的私有(you)成員_number便不能被XmlSerializer進行序列化:

    [Serializable]
    public class Person
    {
        // 私有成員無法被XmlSerializer序列化
        private int _number;
    }

  (4)綜合演示SoapFormatter和(he)XmlSerializer的使用方法(fa):

  ①重新改(gai)寫(xie)Person類

    [Serializable]
    public class Person
    {
        // 私(si)有成員無法被XmlSerializer序(xu)列化
        private int _number;
        // 使用(yong)NonSerialized特性標(biao)記此成員不可被BinaryFormatter和(he)SoapFormatter序列化
        [NonSerialized]
        public string _name;
        // 使用XmlIgnore特性標記此(ci)成員不可悲XmlSerializer序(xu)列化(hua)
        [XmlIgnore]
        public string _univeristy;

        public Person()
        {
        }

        public Person(int i, string s, string u)
        {
            this._number = i;
            this._name = s;
            this._univeristy = u;
        }

        public override string ToString()
        {
            string result = string.Format("學號是:{0},姓名是:{1},大學是:{2}", _number, _name, _univeristy);
            return result;
        }
    }
View Code

  ②新增SoapFormatter和XmlSerializer的序列(lie)(lie)化和反序列(lie)(lie)化方法

    #region 01.SoapFormatter
    // 序列化對(dui)象(xiang)-SoapFormatter
    static byte[] SoapFormatterSerialize(Person p)
    {
        // 使用(yong)Soap協議序列(lie)化(hua)
        IFormatter formatter = new SoapFormatter();
        using (MemoryStream ms = new MemoryStream())
        {
            formatter.Serialize(ms, p);
            return ms.ToArray();
        }
    }

    // 反序列化對象-SoapFormatter
    static Person SoapFormatterDeSerialize(byte[] data)
    {
        // 使用Soap協(xie)議(yi)反序(xu)列化
        IFormatter formatter = new SoapFormatter();
        using (MemoryStream ms = new MemoryStream(data))
        {
            Person p = formatter.Deserialize(ms) as Person;
            return p;
        }
    } 
    #endregion

    #region 02.XmlSerializer
    // 序列化對象(xiang)-XmlSerializer
    static byte[] XmlSerializerSerialize(Person p)
    {
        // 使用XML規(gui)范(fan)序列化
        XmlSerializer serializer = new XmlSerializer(typeof(Person));
        using (MemoryStream ms = new MemoryStream())
        {
            serializer.Serialize(ms, p);
            return ms.ToArray();
        }
    }

    // 反序列(lie)化對象-XmlSerializer
    static Person XmlSerializerDeSerialize(byte[] data)
    {
        // 使(shi)用(yong)XML規范反序列化
        XmlSerializer serializer = new XmlSerializer(typeof(Person));
        using (MemoryStream ms = new MemoryStream(data))
        {
            Person p = serializer.Deserialize(ms) as Person;
            return p;
        }
    } 
    #endregion
View Code

  ③改(gai)寫Main方法進(jin)行測試(shi)

    static void Main(string[] args)
    {
        Person obj = new Person(26, "Edison Chou", "CUIT");
        Console.WriteLine("原(yuan)始對象為:");
        Console.WriteLine(obj.ToString());

        // 使(shi)用(yong)SoapFormatter序列化對象
        byte[] data1 = SoapFormatterSerialize(obj);
        Console.WriteLine("SoapFormatter序列(lie)化后(hou):");
        Console.WriteLine(Encoding.UTF8.GetString(data1));
        Console.WriteLine();
        // 使用XmlSerializer序列化對(dui)象
        byte[] data2 = XmlSerializerSerialize(obj);
        Console.WriteLine("XmlSerializer序列化后:");
        Console.WriteLine(Encoding.UTF8.GetString(data2));

        Console.ReadKey();
    }
View Code

  示(shi)例運行結(jie)果如下(xia)圖所示(shi):

3.5 如何自定義序列化和反序列化的過程?

  對于某些類型,序列化和反序列化往往有一些特殊的操作或邏輯檢查需求,這時就需要我們能夠主動地控制序列化和反序列化的過程。.NET中提供的Serializable特性幫助我們非常快捷地申明了一個可序列化的類型(因此也就缺乏了靈活性),但很多時候由于業務邏輯的要求,我們需要主動地控制序列化和反序列化的過程。因此,.NET提供了ISerializable接口來滿足(zu)自(zi)定義序(xu)列化需求(qiu)。

   下面的代碼展示了自定義序(xu)(xu)列化和反序(xu)(xu)列化的類型模板:

    [Serializable]
    public class MyObject : ISerializable
    {
        protected MyObject(SerializationInfo info, StreamingContext context)
        {
            // 在此構造(zao)方法中實現反序列化(hua)
        }

        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            // 在(zai)此方(fang)法中實現序列化
        }
    }

  如上(shang)代碼所(suo)示,GetObjectData和特殊構造方法(fa)都接收兩個參數:SerializationInfo 類(lei)型(xing)(xing)參數的(de)作用類(lei)似于一個哈希表,通過key/value對(dui)來存儲整個對(dui)象的(de)內容,而StreamingContext 類(lei)型(xing)(xing)參數則包含了流(liu)的(de)當(dang)前(qian)狀態,我們可以根據此參數來判斷是否需要序列(lie)化和反序列(lie)化類(lei)型(xing)(xing)獨享。

  如果基(ji)類實現了(le)ISerializable接(jie)口,則派生類需要針對(dui)自己的成員(yuan)實現反序列化構造方(fang)法(fa),并且(qie)重寫(xie)基(ji)類中的GetObjectData方(fang)法(fa)。

  下面通過一個具體的代(dai)碼示例,來了解如何在.NET程(cheng)序(xu)中自定義(yi)序(xu)列化和反序(xu)列化的過程(cheng):

  ①首先我(wo)們需要一(yi)個需要被序列化和反序列化的類型,該類型有(you)可能被其他類型繼承(cheng)

    [Serializable]
    public class MyObject : ISerializable
    {
        private int _number;
        [NonSerialized]
        private string _name;

        public MyObject(int num, string name)
        {
            this._number = num;
            this._name = name;
        }

        public override string ToString()
        {
            return string.Format("整數是:{0}\r\n字符(fu)串(chuan)是:{1}", _number, _name);
        }

        // 實現自定義的序(xu)列化(hua)
        protected MyObject(SerializationInfo info, StreamingContext context)
        {
            // 從(cong)SerializationInfo對象(類似于一個HashTable)中讀取內容
            this._number = info.GetInt32("MyObjectInt");
            this._name = info.GetString("MyObjectString");
        }

        // 實現自(zi)定義的反序(xu)列化
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            // 將成(cheng)員對象寫(xie)入SerializationInfo對象中
            info.AddValue("MyObjectInt", this._number);
            info.AddValue("MyObjectString", this._name);
        }
    }
View Code

  ②隨后編寫一個繼承自MyObject的子類,并添加一個私有的成員變量。需要注意的是:子類必須負責序列化和反序列化自己添加的成員變量

    [Serializable]
    public class MyObjectSon : MyObject
    {
        // 自己添加的成(cheng)員
        private string _sonName;

        public MyObjectSon(int num, string name)
            : base(num, name)
        {
            this._sonName = name;
        }

        public override string ToString()
        {
            return string.Format("{0}\r\n之類字符串是:{1}", base.ToString(), this._sonName);
        }

        // 實(shi)現自定義反序列化,只負責子類添加的(de)成(cheng)員
        protected MyObjectSon(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            this._sonName = info.GetString("MyObjectSonString");
        }

        // 實現自定義序列(lie)化(hua),只負責子類添加的成員(yuan)
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
            info.AddValue("MyObjectSonString", this._sonName);
        }
    }
View Code

  ③最后編(bian)寫Main方(fang)法(fa),測試自定(ding)義(yi)的序列化和反序列化

    class Program
    {
        static void Main(string[] args)
        {
            MyObjectSon obj = new MyObjectSon(10086, "Edison Chou");
            Console.WriteLine("初始對象為:");
            Console.WriteLine(obj.ToString());
            // 序列(lie)化
            byte[] data = Serialize(obj);
            Console.WriteLine("經過序(xu)列(lie)(lie)化(hua)與(yu)反序(xu)列(lie)(lie)化(hua)之后(hou):");
            Console.WriteLine(DeSerialize(data));

            Console.ReadKey();
        }

        // 序(xu)列(lie)化對象(xiang)-BinaryFormatter
        static byte[] Serialize(MyObject p)
        {
            // 使用(yong)二(er)進(jin)制序(xu)列化(hua)
            IFormatter formatter = new BinaryFormatter();
            using (MemoryStream ms = new MemoryStream())
            {
                formatter.Serialize(ms, p);
                return ms.ToArray();
            }
        }

        // 反序列化對象-BinaryFormatter
        static MyObject DeSerialize(byte[] data)
        {
            // 使用二進制反(fan)序列化
            IFormatter formatter = new BinaryFormatter();
            using (MemoryStream ms = new MemoryStream(data))
            {
                MyObject p = formatter.Deserialize(ms) as MyObject;
                return p;
            }
        }
    }
View Code

  上述代碼的運行結果(guo)如下圖所示:

      

  從結果(guo)圖(tu)中(zhong)可(ke)以(yi)看(kan)出,由于(yu)實現了自定義的(de)(de)序(xu)列化(hua)(hua)和反序(xu)列化(hua)(hua),從而原先使用Serializable特(te)性(xing)的(de)(de)默認序(xu)列化(hua)(hua)和反序(xu)列化(hua)(hua)算(suan)法沒有起作用,MyObject類型的(de)(de)所有成員經(jing)過序(xu)列化(hua)(hua)和反序(xu)列化(hua)(hua)之(zhi)后均被完整地還(huan)原了,包(bao)括申明(ming)了NonSerialized特(te)性(xing)的(de)(de)成員。

參考資料

(1)朱毅(yi),《進入(ru)IT企業(ye)必讀的200個.NET面試題》

(2)張子陽,《.NET之美:.NET關鍵技(ji)術深入(ru)解析》

(3)王濤,《你必(bi)須知道的(de).NET》

(4)solan300,《C#基礎知識梳理之StringBuilder

(5)周旭龍,《ASP.NET WebForm溫故知新

(6)陸敏技,《C#中機密文本的保存方案

 

posted @ 2015-09-18 00:04  EdisonZhou  閱讀(6311)  評論(3)    收藏  舉報