成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

C# 4.0中的協變和逆變

開發 后端
本文介紹如何使用C# 4.0中所引入的“協變和逆變”特性來改進消息執行方式。

在上一篇文章中,我們實現了一個簡單的爬蟲,并指出了這種方式的缺陷。現在,我們就來看一下,如何使用C# 4.0中所引入的“協變和逆變”特性來改進這種消息執行方式,這也是我認為在“普適Actor模型”中最合適的做法。這次,我們動真格的了,我們會一條一條地改進前文提出的缺陷。

協變和逆變
在以前的幾篇文章中,我們一直掛在嘴邊的說法便是消息于Actor類型的“耦合”太高。例如在簡單的爬蟲實現中,Crawler接受的消息為Crawl(Monitor, string),它的第一個參數為Monitor類型。但是在實際應用中,這個參數很可能需要是各種類型,唯一的“約束”只是它必須能夠接受一個ICrawlResponseHandler類型的消息,這樣我們就能把抓取的結果傳遞給它。至于操作Crawler對象的是Monitor還是Robot,還是我們單元測試時動態創建的Mock對象(這很重要),Crawler一概不會關心。

但就是這個約束,在以前的實現中,我們必須讓這個目標繼承Actor< ICrawlResponseHandler>,這樣它也就無法接受其他類型的消息了。例如Monitor還要負責一些查詢操作我們該怎么辦呢?幸運的是,在.NET 4.0(C# 4.0)中,我們只需要讓這個目標實現這樣一個接口即可:

  1. public interface IPort< out T>  
  2. {  
  3.     void Post(Action< T> message);  

瞅到out關鍵字了沒?事實上,還有一個東西您在這里還沒有看到,這便是Action委托在.NET 4.0中的簽名:

  1. public delegate void Action< in T>(T obj); 


就在這樣一個簡單的示例中,協變和逆變所需要的in和out都出現了。這意味著如果有兩個類型Parent和Child,其中Child是Parent的子類(或Parent接口的實現),那么實現了IPort< Child>的對象便可以自動賦值給IPort< Parent>類型的參數或引用1。使用代碼來說明問題可能會更清楚一些:

  1. public class Parent  
  2. {  
  3.     public void ParentMethod() { };  
  4. }  
  5.  
  6. public class Child : Parent { }  
  7.  
  8. static void Main(string[] args)  
  9. {  
  10.     IPort< Child> childPort = new ChildPortType();  
  11.     IPort< Parent> parentPort = childPort; // 自動轉化  
  12.     parentPort.Post(p => p.ParentMethod()); // 可以接受Action< Parent>類型作為消息  
  13. }  


這意味著,我們可以把ICrawlRequestHandler和ICrawlResponseHandler類型寫成下面的形式:

  1. internal interface ICrawlRequestHandler  
  2. {  
  3.     void Crawl(IPort< ICrawlResponseHandler> collector, string url);  
  4. }  
  5.  
  6. internal interface ICrawlResponseHandler  
  7. {  
  8.     void Succeeded(IPort< ICrawlRequestHandler> crawler, string url, string content, List< string> links);  
  9.     void Failed(IPort< ICrawlRequestHandler> crawler, string url, Exception ex);  
  10. }  


如今,Monitor和Crawler便可以寫成如下模樣:

  1. internal class Crawler : Actor< Action< Crawler>>, IPort< Crawler>, ICrawlRequestHandler  
  2. {  
  3.     protected override void Receive(Action< Crawler> message) { message(this); }  
  4.  
  5.     #region ICrawlRequestHandler Members  
  6.  
  7.     void ICrawlRequestHandler.Crawl(IPort< ICrawlResponseHandler> collector, string url)  
  8.     {  
  9.         try 
  10.         {  
  11.             string content = new WebClient().DownloadString(url);  
  12.  
  13.             var matches = Regex.Matches(content, @"href=""(http://[^""]+)""").Cast< Match>();  
  14.             var links = matches.Select(m => m.Groups[1].Value).Distinct().ToList();  
  15.             collector.Post(m => m.Succeeded(this, url, content, links));  
  16.         }  
  17.         catch (Exception ex)  
  18.         {  
  19.             collector.Post(m => m.Failed(this, url, ex));  
  20.         }  
  21.     }  
  22.  
  23.     #endregion  
  24. }  
  25.  
  26. public class Monitor : Actor< Action< Monitor>>, IPort< Monitor>, ICrawlResponseHandler  
  27. {  
  28.     protected override void Receive(Action< Monitor> message) { message(this); }  
  29.  
  30.     #region ICrawlResponseHandler Members  
  31.  
  32.     void ICrawlResponseHandler.Succeeded(IPort< ICrawlRequestHandler> crawler,  
  33.         string url, string content, List< string> links) { ... }  
  34.  
  35.     void ICrawlResponseHandler.Failed(IPort< ICrawlRequestHandler> crawler,  
  36.         string url, Exception ex) { ... }  
  37.  
  38.     #endregion  
  39.  
  40.     private void DispatchCrawlingTasks(IPort< ICrawlRequestHandler> reusableCrawler)  
  41.     {  
  42.         if (this.m_readyToCrawl.Count < = 0)  
  43.         {  
  44.             this.WorkingCrawlerCount--;  
  45.         }  
  46.  
  47.         var url = this.m_readyToCrawl.Dequeue();  
  48.         reusableCrawler.Post(c => c.Crawl(this, url));  
  49.  
  50.         while (this.m_readyToCrawl.Count > 0 &&  
  51.             this.WorkingCrawlerCount <  this.MaxCrawlerCount)  
  52.         {  
  53.             var newUrl = this.m_readyToCrawl.Dequeue();  
  54.             IPort< ICrawlRequestHandler> crawler = new Crawler();  
  55.             crawler.Post(c => c.Crawl(this, newUrl));  
  56.  
  57.             this.WorkingCrawlerCount++;  
  58.         }  
  59.     }  
  60. }  


Monitor的具體實現和上篇文章區別不大,您可以參考文章末尾給出的完整代碼,并配合前文的分析來理解,這里我們只關注被標紅的兩行代碼。

在第一行中我們創建了一個Crawler類型的對象,并把它賦值給IPort< ICrawlerRequestHandler>類型的變量中。請注意,Crawler對象并沒有實現這個接口,它只是實現了IPort< Crawler>及ICrawlerRequestHandler。不過由于IPort< T>支持協變,于是IPort< Crawler>被安全地轉換成了IPort< ICrawlerRequestHandler>對象。

第二行中再次發生了協變:ICrawlRequestHandler.Crawel的第一個參數需要IPort< ICrawlResponseHandler>類型的對象,但是this是Monitor類型的,它并沒有實現這個接口。不過,和上面描述的一樣,由于IPort< T>支持協變,因此這樣的類型轉化是安全的,允許的。于是在Crawler類便可以操作一個“抽象”,而不是具體的Monitor類型來辦事了。

神奇不?但就是這么簡單。

“內部”消息控制
在上一篇文章中,我們還提出了Crawler實現的另一個缺點:沒有使用異步IO。WebClient本身的DownloadStringAsync方法可以進行異步下載,但是如果在異步完成的后續操作(如分析鏈接)會在IO線程池中運行,這樣我們就很難對任務所分配的運算能力進行控制。我們當時提出,可以把后續操作作為消息發送給Crawler本身,也就是進行“內部”消息控制——可惜的是,我們當時無法做到。不過現在,由于Crawler實現的是IPort< Crawler>接口,因此,我們可以把Crawler內部的任何方法作為消息傳遞給自身,如下:

  1. internal class Crawler : Actor< Action< Crawler>>, IPort< Crawler>, ICrawlRequestHandler  
  2. {  
  3.     protected override void Receive(Action< Crawler> message) { message(this); }  
  4.  
  5.     #region ICrawlRequestHandler Members  
  6.  
  7.     public void Crawl(IPort< ICrawlResponseHandler> collector, string url)  
  8.     {  
  9.         WebClient client = new WebClient();  
  10.         client.DownloadStringCompleted += (sender, e) =>  
  11.         {  
  12.             if (e.Error == null)  
  13.             {  
  14.                 this.Post(c => c.Crawled(collector, url, e.Result));  
  15.             }  
  16.             else 
  17.             {  
  18.                 collector.Post(c => c.Failed(this, url, e.Error));  
  19.             }  
  20.         };  
  21.  
  22.         client.DownloadStringAsync(new Uri(url));  
  23.     }  
  24.  
  25.     private void Crawled(IPort< ICrawlResponseHandler> collector, string url, string content)  
  26.     {  
  27.         var matches = Regex.Matches(content, @"href=""(http://[^""]+)""").Cast< Match>();  
  28.         var links = matches.Select(m => m.Groups[1].Value).Distinct().ToList();  
  29.  
  30.         collector.Post(c => c.Succeeded(this, url, content, links));  
  31.     }  
  32.  
  33.     #endregion  
  34. }  


我們準備了一個private的Crawled方法,如果抓取成功了,我們會把這個方法的調用封裝在一條消息中重新發給自身。請注意,這是個私有方法,因此這里完全是在做“內部”消息控制。

開啟抓取任務
在上一篇文章中,我們為Monitor添加了一個Start方法,它的作用是啟動URL。我們知道,對單個Actor來說消息的處理是線程安全的,但是這個前提是使用“消息”傳遞的方式進行通信,如果直接調用Start公有方法,便會破壞這種線程安全特性。不過現在的Monitor已經不受接口的限制,可以自由接受任何它可以執行的消息,因此我們只要對外暴露一個Crawl方法即可:

  1. public class Monitor : Actor< Action< Monitor>>, IPort< Monitor>,  
  2.     ICrawlResponseHandler,  
  3.     IStatisticRequestHandelr  
  4. {  
  5.     ...  
  6.  
  7.     public void Crawl(string url)  
  8.     {  
  9.         if (this.m_allUrls.Contains(url)) return;  
  10.         this.m_allUrls.Add(url);  
  11.  
  12.         if (this.WorkingCrawlerCount <  this.MaxCrawlerCount)  
  13.         {  
  14.             this.WorkingCrawlerCount++;  
  15.             IPort< ICrawlRequestHandler> crawler = new Crawler();  
  16.             crawler.Post(c => c.Crawl(this, url));  
  17.         }  
  18.         else 
  19.         {  
  20.             this.m_readyToCrawl.Enqueue(url);  
  21.         }  
  22.     }  
  23. }  


于是我們便可以向Monitor發送消息,讓其抓取特定的URL:

  1. string[] urls =  
  2. {  
  3.     "http://www.cnblogs.com/dudu/",  
  4.     "http://www.cnblogs.com/TerryLee/",  
  5.     "http://www.cnblogs.com/JeffreyZhao/" 
  6. };  
  7.  
  8. Random random = new Random(DateTime.Now.Millisecond);  
  9. Monitor monitor = new Monitor(10);  
  10. foreach (var url in urls)  
  11. {  
  12.     var urlToCrawl = url;  
  13.     monitor.Post(m => m.Crawl(urlToCrawl));  
  14.     Thread.Sleep(random.Next(1000, 3000));  
  15. }  
  16.  

上面的代碼會每隔1到3秒發出一個抓取請求。由于我們使用了消息傳遞的方式進行通信,因此對于Monitor來說,這一切都是線程安全的。我們可以隨時隨地為Monitor添加抓取任務。

接受多種消息(協議)
我們再觀察一下Monitor的簽名:

class Monitor : Actor< Action< Monitor>>, IPort< Monitor>, ICrawlResponseHandler
可以發現,如今的Monitor已經和它實現的協議沒有一對一的關系了。也就是說,它可以添加任意功能,可以接受任意類型的消息,我們只要讓它實現另一個接口即可。于是乎,我們再要一個“查詢”功能2:

  1. public interface IStatisticRequestHandelr  
  2. {  
  3.     void GetCrawledCount(IPort< IStatisticResponseHandler> requester);  
  4.     void GetContent(IPort< IStatisticResponseHandler> requester, string url);  
  5. }  
  6.  
  7. public interface IStatisticResponseHandler  
  8. {  
  9.     void ReplyCrawledCount(int count);  
  10.     void ReplyContent(string url, string content);  
  11. }  


為了讓Monior支持查詢,我們還需要為它添加這樣的代碼:

  1. public class Monitor : Actor< Action< Monitor>>, IPort< Monitor>,  
  2.     ICrawlResponseHandler,  
  3.     IStatisticRequestHandelr  
  4. {  
  5.     ...  
  6.  
  7.     #region IStatisticRequestHandelr Members  
  8.  
  9.     void IStatisticRequestHandelr.GetCrawledCount(IPort< IStatisticResponseHandler> requester)  
  10.     {  
  11.         requester.Post(r => r.ReplyCrawledCount(this.m_urlContent.Count));  
  12.     }  
  13.  
  14.     void IStatisticRequestHandelr.GetContent(IPort< IStatisticResponseHandler> requester, string url)  
  15.     {  
  16.         string content;  
  17.         if (!this.m_urlContent.TryGetValue(url, out content))  
  18.         {  
  19.             content = null;  
  20.         }  
  21.  
  22.         requester.Post(r => r.ReplyContent(url, content));  
  23.     }  
  24.  
  25.     #endregion  
  26. }  


最后,我們來嘗試著使用這個“查詢”功能。首先,我們編寫一個測試用的TestStatisticPort類:

  1. public class TestStatisticPort : IPort< IStatisticResponseHandler>, IStatisticResponseHandler  
  2. {  
  3.     private IPort< IStatisticRequestHandelr> m_statisticPort;  
  4.  
  5.     public TestStatisticPort(IPort< IStatisticRequestHandelr> statisticPort)  
  6.     {  
  7.         this.m_statisticPort = statisticPort;  
  8.     }  
  9.  
  10.     public void Start()  
  11.     {  
  12.         while (true)  
  13.         {  
  14.             Console.ReadLine();  
  15.             this.m_statisticPort.Post(s => s.GetCrawledCount(this));  
  16.         }  
  17.     }  
  18.  
  19.     #region IPort< IStatisticResponseHandler> Members  
  20.  
  21.     void IPort< IStatisticResponseHandler>.Post(Action< IStatisticResponseHandler> message)  
  22.     {  
  23.         message(this);  
  24.     }  
  25.  
  26.     #endregion  
  27.  
  28.     #region IStatisticResponseHandler Members  
  29.  
  30.     void IStatisticResponseHandler.ReplyCrawledCount(int count)  
  31.     {  
  32.         Console.WriteLine("Crawled: {0}", count);  
  33.     }  
  34.  
  35.     void IStatisticResponseHandler.ReplyContent(string url, string content) { ... }  
  36.  
  37.     #endregion  
  38. }  


當調用Start方法時,控制臺將會等待用戶敲擊回車鍵。當按下回車鍵時,TestStatisticPort將會向Monitor發送一個IStatisticRequestHandler.GetCrawledCount消息。Monitor回復之后,屏幕上便會顯示當前已經抓取成功的URL數目。例如,我們可以編寫如下的測試代碼:

  1. static void Main(string[] args)  
  2. {  
  3.     var monitor = new Monitor(5);  
  4.     monitor.Post(m => m.Crawl("http://www.cnblogs.com/"));  
  5.  
  6.     TestStatisticPort testPort = new TestStatisticPort(monitor);  
  7.     testPort.Start();  
  8. }  


隨意敲擊幾下回車,結果如下:

Crawl Statistic


總結
如今的做法,兼顧了強類型檢查,并使用C# 4.0中的協變和逆變特性,把上一篇文章中提出的問題解決了,不知您是否理解了這些內容?只可惜,我們在C# 3.0中還沒有協變和逆變。因此,我們還必須思考一個適合C# 3.0的做法。

順便一提,由于F#不支持協變和逆變,因此本文的做法無法在F#中使用。

注1:關于協變和逆變特性,我認為腦袋兄的這篇文章講的非常清楚——您看得頭暈了?是的,剛開始了解協變和逆變,以及它們之間的嵌套規則時我也頭暈,但是您在掌握之后就會發現,這的確是一個非常有用的特性。

注2:不知您是否發現,與之前internal的Crawl相關接口不同,Statistic相關接口是public的。我們在使用接口作為消息時,也可以通過這種辦法來控制哪些消息是可以對外暴露的。這也算是一種額外的收獲吧。

【編輯推薦】

  1. C#調用Windows API函數
  2. 詳解C#調用Outlook API
  3. C#連接Access、SQL Server數據庫
  4. 介紹C#調用API的問題
  5. C#調用Excel與附加代碼
責任編輯:yangsai 來源: 博客園
相關推薦

2011-01-14 10:27:18

C#.netasp.net

2009-05-27 11:30:20

C#Visual Stud協變

2012-03-13 09:32:15

C#協變

2009-06-03 14:50:17

C# 4.0泛型協變性

2022-04-18 20:12:03

TypeScript靜態類型JavaScrip

2020-08-03 08:13:51

Vue3TypeScript

2020-09-29 06:37:30

Java泛型

2009-02-03 09:33:26

動態類型動態編程C# 4.0

2009-10-20 15:03:29

ExpandoObje

2009-07-22 09:27:04

Scala變高變寬

2020-02-11 14:14:52

this函數

2015-12-01 18:03:44

EMUI4.0

2013-10-31 09:36:43

程序員程序高手

2023-01-29 09:15:42

2009-09-01 09:38:45

COM互操作性

2011-08-08 10:47:45

超微機箱服務器

2022-04-13 11:18:48

滲透測試Mock

2009-08-19 16:51:14

C# 4.0 dyna

2024-03-19 14:41:08

C#操作符開發
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美国产激情二区三区 | 日韩精品一区二区三区 | 爱爱综合网 | 欧美一级在线 | 国产精品久久久久久久久久三级 | 欧美精选一区二区 | 免费99视频 | 亚洲精品乱码久久久久v最新版 | 成人美女免费网站视频 | 日韩国产精品一区二区三区 | 九九色综合 | 久久99精品久久久 | 澳门永久av免费网站 | 一a一片一级一片啪啪 | 欧美综合久久 | 亚洲欧美v | 久久高潮 | 国产精品高潮呻吟久久 | 免费国产视频在线观看 | 欧美日韩国产精品一区 | 99精品观看| 在线观看中文字幕 | 中文字幕欧美一区 | 91综合网| 欧美成人手机视频 | 国产一区二区三区视频免费观看 | 成人影视网 | 人人操日日干 | 午夜免费看 | 亚洲国产欧美91 | 国产精品不卡 | 国产免费一区二区 | 亚洲网站在线观看 | 成人影院一区二区三区 | 在线日韩av电影 | 成人网在线 | 精品福利视频一区二区三区 | 国产视频福利一区 | 午夜视频在线免费观看 | 国产成人高清在线观看 | 亚洲成人观看 |