C#複習筆記(4)--C#3:革新寫代碼的方式(擴充方法)

來源:互聯網
上載者:User

標籤:一般來說   最簡   ima   stack   執行個體成員   iat   操作符   image   att   

 

擴充方法

擴充方法有以下幾個需求:

 

  • 你想為一個類型添加一些 成員;
  • 你不需要為類型的執行個體添加任何更多的資料;
  • 你不能改變類型本身, 因為是別人的代碼。

對於C#1和C#2中的靜態方法,擴充方法是一種更優雅的解決方案。

文法

並不是任何方法都能作為擴充方法使用—— 它必須具有以下特徵:

  • 它必須在一個非嵌套的、 非泛型的靜態類中( 所以必須是一 個靜態方法);
  • 它至少要有 一個參數;
  • 第一個參數必須附加 this 關鍵字作為首碼;
  • 第一個參數不能有其他任何修飾 符(比如out或ref);
  • 第一個參數的類型不能是指標類型。

我們來試著給Stream類寫一個擴充方法:

 public static class StreamUtil    {        const int bufferSize = 8192;        public static void CopyToo(this Stream inputStream, Stream outPutStream)        {            var buffer = new byte[bufferSize];            int read;            while ((read=inputStream.Read(buffer,0,buffer.Length))>0)            {                outPutStream.Write(buffer,0,read);            }        }        public static byte[] ReadFull(this Stream input)        {            using (MemoryStream stream=new MemoryStream())            {                CopyToo(input,stream);                return stream.ToArray();            }        }    }

擴充方法必須在頂級的靜態類中進行聲明,不能是嵌套的靜態類。

擴充方法假裝自己是另一個類的執行個體方法,來看看如何使用:

 static void Main(string[] args)        {            WebRequest request = WebRequest.Create("https://www.baidu.com");            using (WebResponse response = request.GetResponse())            using (var resStream = response.GetResponseStream())                using(var outPut=File.Open(@"C:\Users\jianxin\Desktop\test.txt",FileMode.Open))            {              resStream.CopyToo(outPut);            }            Console.WriteLine("done!");            Console.ReadKey();        }

之所以吧CopyTo改成CopyToo是因為Stream現在已經實現了這個擴充方法了。如果和執行個體方法同名,則不會去調用這個擴充方法。

一些原理

一般來說,如果你在一個對象後面調用這個對象的成員比如方法,編譯器會首先從它的執行個體成員中去尋找,如果沒有找到,他會在引入的命名空間裡面去尋找合適的擴充方法。

為了決定是否使用 一個擴充方法, 編譯器必須能區分擴充方法與某靜態類中恰好具有合適簽名的其他方法。 為此, 它會檢查類和方法是否具有System.Runtime.CompilerServices.ExtensionAttribute 這個特性, 它是.NET 3. 5 新增的。 但是,編譯器不檢查特性來自哪個程式集。這意味著你可以在C#2或早前的版本中自己編寫一個這個屬性類別來滿足編譯器的這種搜尋策略。但是,誰特麼還在用C#2或1呢?如果遇到多個合適的版本,還是會用“更好的選擇”原則來選用最合適的那一個。

在Null 參考上面調用擴充方法

在Null 參考上面調用方法會導致NullRefrenceException的異常。但是可以調用擴充方法而不會導致異常。

public static class NullUtil    {        public static bool IsNull(this object obj)        {return obj==null;  
}
}
 static void Main(string[] args)        {            object obj = null;            Console.WriteLine(obj.IsNull());//true            obj = new object();            Console.WriteLine(obj.IsNull());//false            Console.ReadKey();        }

如果IsNull是一個執行個體方法那麼會引發NullRefrenceException,但是擴充方法不會,可以試一試,很爽。這個寫法與string.IsNullOrEmpty()形成了鮮明的對比。

Enumerable

LINQ差不多全部的功能都是用Enumerable和Queryable的擴充方法來得到的。

Enumerable中有一個不是擴充方法:Range

 var collection = Enumerable.Range(0, 10);            foreach (int item in collection)            {                Console.WriteLine(item);            }

講這個例子並不是因為它很特殊,是因為它的一個特性:順延強制 。Range方法並不會真的構造含有適當數位列表,它只是在恰當的時間產生那些數。 換言之,構造的可枚舉的執行個體並不會做大部分工作。 它只是將東西準備好, 使資料能在適當的位置以一種“just-in-time” 的方式提供。 這稱為順延強制, 是LINQ的一個核心部分。

可以根據一個可枚舉的執行個體返回另一個可枚舉的執行個體,這在LINQ中是很常見的:collection.Reverse();

緩衝和流式技術

架構提供的擴充方法會盡量嘗試對資料進行“ 流 式”(stream)或者說“管道”(pipe)傳輸。 要求一個迭代器提供下一個元素時, 它通常會從它連結的迭代器擷取一個元素, 處理那個元素, 再返回符合要求的結果, 而不用佔用自己更多的儲存空間。 執行簡單的轉換和 過濾操作時, 這樣做非常簡單, 可用的資料處理起來也非常高效。 但是,對於某些操作來說, 比如反轉或排序, 就要求所有資料都處於可用狀態, 所以需要載入所有資料到記憶體來執行批處理。 緩衝和管道傳輸方式, 這兩者的差別很像是載入整個DataSet讀取資料和用 一個DataReader來每次處理一條記錄的差別。 使用LINQ時務必想好真正需要的是什麼, 一個簡單的方法調用可能會嚴重影響效能。

串流(streaming) 也叫惰性求值(lazy evaluation),緩衝傳輸(bufferring)也叫熱情求值(eager evaluation)。 例如,Reverse方法使用了順延強制(deferred execution) , 它在第一次調用MoveNext之前不做任何事情。 但隨後卻熱切地(eagerly) 對資料來源求值。

惰性求值和熱情求值都屬於順延強制的求值方式, 與立即執行(immediately execution)相對。 Stack Overflow上的一個文章很好地闡述了它們之間的區別( 參見 http://stackoverflow.com/questions/2515796/deferred-execution-and-eager-evaluation)。

用where過濾並將方法調用連結在一起

where擴充方法是對集合進行過濾的一種簡單但又十分強大的方式,看一下代碼:

 static void Main(string[] args)        {            var collection = Enumerable.Range(0, 10)
.Where(x=>x%2!=0)
.Reverse(); foreach (int item in collection) { Console.WriteLine(item); } Console.ReadKey(); }

上述代碼使用where過濾掉了序列中的所有偶數,然後使用Reverse對序列進行了反轉。希望你此時已經注意到了一個模式—— 我們將方法調用連結到一起 了。string.Replace()就是這樣的一個模式。LINQ針對資料處理進行了專門的調整,將各個單獨的操作鏈結接在一起形成了一個管道,然後讓資訊在這個管道中流通。

有一個效率問題:上面的代碼如果先調用Reverse再調用where的話會和之前的調用順序的效率相同嗎?不會,Reverse必須計算出偶數,而偶數最終是要被拋棄的。而先用where過濾掉這部分資料後,Reverse要執行的計算明顯變小了。

用select方法和匿名型別進行投影

Enumerable中最重要的投影方法就是select,它操縱一個IEnumerable<TSource>,把他轉化成一個IEnumerable<TResult>.它利用了順延強制的技術,只有在每個元素被請求時才真正的執行投影。

static void Main(string[] args)        {            var collection = Enumerable.Range(0, 10)                .Where(x => x % 2 != 0)                .Reverse()                .Select(x => new { Original = x, SquareRoot = Math.Sqrt(x) });            foreach (var item in collection)            {                Console.WriteLine(item);            }            Console.ReadKey();        }
用OrderBy進行排序

在Linq中,一般是通過OrderBy或OrderByDescending。也可以繼續排序。使用ThenBy和ThenByDescending。需要注意的就一點,排序不會改變原有集合,他會返回一個新的序列----LINQ操作符是無副作用 的:它們不會影響輸入, 也不會改變環境。 除非你迭代的是一 個自然狀態序列( 如從網路流中讀取資料) 或使用含有副作用的委託參數。 這是函數式編程的方法, 可以使代碼更加 可讀、可測、可組合、可 預測、健壯並且安全執行緒。

GroupBy分組

假設要觀察程式出現的bug的數量,對他們種類進行分組:

bugs.GroupBy(bug => bug.AssignedTo)                .Select(list => new { Developer = list.Key, Count = list.Count() })                .OrderByDescending(x => x.Count);

 結果是一個IGrouping<TKey, TElement>。 GroupBy有多個重載版本, 這裡使用的是最簡單的。 然後選擇鍵(開發人員的姓名) 和分配給他們的bug的數量。 之後,我們對結果進行排序, 最先顯示分配到bug數量最多的開發人員。

研究Enumerable類時, 往往會感覺搞不清楚具體發生的事情——例如,GroupBy的一個重載版本居然有4個型別參數和5個“普通”參數(3個是委託)。但是,不要驚慌——只要按照上一章描述的步驟慢慢梳理,將不同的類型賦給不同的型別參數, 直到清楚呈現出方法的樣子。這樣,理解起來就容易多了。 這些例子不是具體針對某個方法調用, 但我希望你能體會到將方法調用連結起來之後所發揮的巨大作用。 在這個鏈條中, 每個方法都擷取一個原創組合, 並以某種形式返回另一個原創組合——中間可能過濾掉一些值,可能對它們進行排序,可能轉換每一個元素, 可能彙總某些值, 或者做其他處理。 在許多情況下, 最終的代碼都易讀、易懂。在其他情況下, 它最起碼也會比使用以前版本的C#寫的等價代碼簡單得多。

使用思路和原則流暢介面

因為擴充方法支援這種鏈式的調用。所以,才有了流暢介面的這個概念,比如OrderBy ThenBy等。就和自然語言一樣。

 

C#複習筆記(4)--C#3:革新寫代碼的方式(擴充方法)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.