<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      代碼改變世界

      .NET中*延遲*特性的幾個陷阱

      2009-06-08 13:19  Jeffrey Zhao  閱讀(26060)  評論(79)    收藏  舉報

      .NET發展至今,其實各處都有“延遲(Lazy)”的痕跡,一個小小的“Laziness”給我們帶來了不少靈活性。“延遲”的關鍵就在于“只在需要的時候處理數據”,老趙曾經在多篇文章中提到了類似的概念,如《高階函數、委托與匿名方法》及《您善于使用匿名函數嗎?》。不過“延遲”本身也會給您帶來一些陷阱,某些陷阱您很有可能也曾經遇到過。這篇文章便是總結了延遲特性的集中常見陷阱,并給出應對方案。

      重復運算

      問題

      “延遲”的本意是“減少計算”,但是如果您使用不當,很可能反而會造成“重復計算”。例如,我們首先構建一個方法,它接受一個參數n,返回一個Func<int, bool>對象:

      static Func<int, bool> DivideBy(int n)
      {
          return x =>
          {
              bool divisible = x % n == 0;
              Console.WriteLine(
                  "{0} can be divisible by {1}? {2}",
                  x, n, divisible ? "Yes" : "No");
              return divisible;
          };
      }
      

      返回的Func<int, bool>對象會根據傳入的參數x,返回一個表示x能否被n整除的布爾值。在這過程中,還會向控制臺輸出一句話,例如:“10 can be divisible by 3? No”。每當看到這句話,則表明“經過了一次判斷”。那么您是否知道,下面的代碼會輸出什么結果呢?

      List<int> values = new List<int>();
      for (int i = 0; i < 10; i++) values.Add(i);
      
      var divideByTwo = values.Where(DivideBy(2));
      var divideByTwoAndThree = divideByTwo.Where(DivideBy(3));
      var divideByTwoAndFive = divideByTwo.Where(DivideBy(5));
      
      foreach (var i in divideByTwoAndThree) { }
      foreach (var i in divideByTwoAndFive) { }
      

      結果如下:

      0 can be divisible by 2? Yes
      0 can be divisible by 3? Yes
      1 can be divisible by 2? No
      2 can be divisible by 2? Yes
      2 can be divisible by 3? No
      3 can be divisible by 2? No
      4 can be divisible by 2? Yes
      4 can be divisible by 3? No
      5 can be divisible by 2? No
      6 can be divisible by 2? Yes
      6 can be divisible by 3? Yes
      7 can be divisible by 2? No
      8 can be divisible by 2? Yes
      8 can be divisible by 3? No
      9 can be divisible by 2? No
      0 can be divisible by 2? Yes
      0 can be divisible by 5? Yes
      1 can be divisible by 2? No
      2 can be divisible by 2? Yes
      2 can be divisible by 5? No
      3 can be divisible by 2? No
      4 can be divisible by 2? Yes
      4 can be divisible by 5? No
      5 can be divisible by 2? No
      6 can be divisible by 2? Yes
      6 can be divisible by 5? No
      7 can be divisible by 2? No
      8 can be divisible by 2? Yes
      8 can be divisible by 5? No
      9 can be divisible by 2? No

      您是否發現,無論是在遍歷divideByTwoAndThree和divideByTwoAndFive序列時,都會從原有的values序列里重新判斷每個元素是否能夠被2整除?這就是.NET 3.5中“Where”的延遲特性,如果您在這里沒有意識到這點,就可能會產生重復計算,浪費了計算能力。

      解決方案

      解決這個問題的方法就是在合適的時候進行“強制計算”。例如:

      var divideByTwo = values.Where(DivideBy(2)).ToList();
      var divideByTwoAndThree = divideByTwo.Where(DivideBy(3));
      var divideByTwoAndFive = divideByTwo.Where(DivideBy(5));
      

      結果就變成了:

      0 can be divisible by 2? Yes
      1 can be divisible by 2? No
      2 can be divisible by 2? Yes
      3 can be divisible by 2? No
      4 can be divisible by 2? Yes
      5 can be divisible by 2? No
      6 can be divisible by 2? Yes
      7 can be divisible by 2? No
      8 can be divisible by 2? Yes
      9 can be divisible by 2? No
      0 can be divisible by 3? Yes
      2 can be divisible by 3? No
      4 can be divisible by 3? No
      6 can be divisible by 3? Yes
      8 can be divisible by 3? No
      0 can be divisible by 5? Yes
      2 can be divisible by 5? No
      4 can be divisible by 5? No
      6 can be divisible by 5? No
      8 can be divisible by 5? No
      

      此時,在獲得divideByTwo序列時,就會立即進行計算,這樣在遍歷后兩者時就不會重復計算1,3,5等元素了。

      異常陷阱

      問題

      請問您是否知道下面的代碼有什么問題?

      public static IEnumerable<string> ToString(IEnumerable<int> source)
      {
          if (source == null)
          {
              throw new ArgumentNullException("source");
          }
      
          foreach (int item in source)
          {
              yield return item.ToString();
          }
      }
      

      如果您沒有看出來的話,不如運行一下這段代碼:

      static void Main(string[] args)
      {
          IEnumerable<string> values;
          try
          {
              values = ToString(null);
          }
          catch (ArgumentNullException)
          {
              Console.WriteLine("Passed the null source");
              return;
          }
      
          foreach (var s in values) { }
      }
      

      請問,運行上面的代碼是否會拋出異常?從代碼的意圖上看,在ToString方法的一開始我們會檢查參數是否為null,然后拋出異常——這本應被catch語句所捕獲。但是事實上,代碼直到foreach執行時才真正拋出了異常。這種“延遲”執行違反了我們的實現意圖。為什么會這樣呢?您可以使用.NET Reflector反編譯一下,查看一下yield語句的等價C#實現是什么樣的,一切就清楚了。

      解決方案

      對于這個問題,一般我們可以使用一對public和private方法配合來使用:

      public static IEnumerable<string> ToString(IEnumerable<int> source)
      {
          if (source == null)
          {
              throw new ArgumentNullException("source");
          }
      
          return ToStringInternal(source);
      }
      
      private static IEnumerable<string> ToStringInternal(IEnumerable<int> source)
      {
          foreach (int item in source)
          {
              yield return item.ToString();
          }
      }
      

      不妨再去查看一下現在的C#代碼實現?

      資源管理

      問題

      由于是延遲執行,一些原本最簡單的代碼模式可能就破壞了。例如:

      static Func<string> ReadAllText(string file)
      { 
          using (Stream stream = File.OpenRead(file))
          {
              StreamReader reader = new StreamReader(stream);
              return reader.ReadToEnd;
          }
      }
      

      使用using來管理文件的打開關閉是最容易不過的事情了,不過現在如果您通過ReadAllText(@"C:\abc.txt")方法獲得的Func<string>對象,在執行時就會拋出ObjectDisposedException。這是因為原本我們意圖中的順序:

      1. 打開文件
      2. 讀取內容
      3. 關閉文件

      因為有“延遲”特性,這個順序已經變為:

      1. 打開文件
      2. 關閉文件
      3. 讀取內容

      這怎么能不出錯?

      解決方案

      有朋友說,這個容易:

      static Func<string> ReadAllText(string file)
      { 
          using (Stream stream = File.OpenRead(file))
          {
              StreamReader reader = new StreamReader(stream);
              string text = reader.ReadToEnd();
      
              return () => text;
          }
      }
      

      的確沒有拋出異常了,但是這也喪失了“延遲”的特點了。我們必須讓它能夠在調用委托對象的時候,才去打開文件:

      static Func<string> ReadAllText(string file)
      {
          return () =>
          {
              using (Stream stream = File.OpenRead(file))
              {
                  StreamReader reader = new StreamReader(stream);
                  return reader.ReadToEnd();
              }
          };
      }
      

      值得一提的是,using完全可以配合yield語句使用。也就是說,您可以編寫這樣的代碼:

      static IEnumerable<string> AllLines(string file)
      {
          using (Stream stream = File.OpenRead(file))
          {
              StreamReader reader = new StreamReader(stream);
              while (!reader.EndOfStream)
              {
                  yield return reader.ReadLine();
              }
          }
      } 

      由此也可見C#編譯器是多么的強大,它幫我們解決了非常重要的問題。

      閉包共享

      問題

      其實這個問題也已經被談過很多次了,在這里提一下主要是為了保持內容的完整性。您認為,以下代碼結果如何?

      List<Action> actions = new List<Action>();
      for (int i = 0; i < 10; i++)
      {
          actions.Add(() => Console.WriteLine(i));
      }
      
      foreach (var a in actions) a();
      

      它打印出來的結果是10個10,具體原因在《警惕匿名方法造成的變量共享》一文中已經有過描述,概括而來便是:各個action共享一個閉包,導致其中的“i”并不是獨立的。

      解決方案

      解決這個問題的方法,只需讓不同閉包訪問的值相互獨立即可。如:

      List<Action> actions = new List<Action>();
      for (int i = 0; i < 10; i++)
      {
          int  j = i; // 新增代碼
          actions.Add(() => Console.WriteLine(j));
      }
      
      foreach (var a in actions) a();
      

       

      關于“延遲”特性,您還有什么看法呢?

      主站蜘蛛池模板: 日韩人妻无码精品久久| 亚洲综合精品第一页| 国产欧美综合在线观看第十页| 日韩中文字幕亚洲精品| 国产果冻豆传媒麻婆精东| 在线看片免费不卡人成视频| 国产精品无码成人午夜电影| 国产黄色一区二区三区四区| 亚洲产在线精品亚洲第一站一 | 国产乱码精品一区二区三区中文 | 人妻饥渴偷公乱中文字幕| 国产亚洲人成网站在线观看| 国语精品一区二区三区| 欧美日本精品一本二本三区| 亚洲中文字幕无码中字| 国产无吗一区二区三区在线欢| 国产日产免费高清欧美一区| 亚洲乱码精品中文字幕| yw尤物av无码国产在线观看| 国产精品视频午夜福利| 国产精品成人va在线播放| 久久人人妻人人爽人人爽| 亚洲一区二区三区四区三级视频 | 高潮videossex潮喷| 国产目拍亚洲精品二区| 国产99精品成人午夜在线 | 精品国产熟女一区二区三区| 亚洲乱理伦片在线观看中字| 免费a级黄毛片| 久久精品午夜视频| 国产初高中生粉嫩无套第一次| 亚洲男人第一无码av网站| 久激情内射婷内射蜜桃| 日韩一区日韩二区日韩三区 | 国产一区在线播放av| 狠狠色狠狠色综合久久蜜芽| 在线观看国产成人av片| 国产亚洲精品第一综合| 坐盗市亚洲综合一二三区| 97人妻成人免费视频| 一本大道久久香蕉成人网|