試試 IEnumerable 的 10 個小例子

  • 2019 年 10 月 3 日
  • 筆記

IEnumerable 接口是 C# 開發過程中非常重要的接口,對於其特性和用法的了解是十分必要的。本文將通過10個小例子,來熟悉一下其簡單的用法。

全是源碼

以下便是這10個小例子,響應的說明均標記在注釋中。

每個以 TXX 開頭命名的均是一個示例。建議從上往下閱讀。

using System;  using System.Collections.Generic;  using System.Configuration;  using System.IO;  using System.Linq;  using System.Net.Http;  using System.Threading.Tasks;  using FluentAssertions;  using Xunit;  using Xunit.Abstractions;    namespace Try_More_On_IEnumerable  {      public class EnumerableTests      {          private readonly ITestOutputHelper _testOutputHelper;            public EnumerableTests(              ITestOutputHelper testOutputHelper)          {              _testOutputHelper = testOutputHelper;          }            [Fact]          public void T01普通的循環獲取偶數()          {              // 創建一個集合存放最終結果              var result = new List<int>();              for (var i = 0; i <= 10; i++)              {                  // 如果是偶數的話,將數字放入集合中                  if (i % 2 == 0)                  {                      result.Add(i);                  }              }                // 斷言最終的結果              result.Should().Equal(0, 2, 4, 6, 8, 10);          }            [Fact]          public void T02普通Enumerable()          {              // 通過靜態方法獲取偶數的Enumerable對象              var result = GetNumber(10);                // 斷言結果              result.Should().Equal(0, 2, 4, 6, 8, 10);                /**               * 這種寫法比起 T01 的寫法少了一個集合的創建,通常來說是性能更佳的寫法               */          }            /// <summary>          /// 獲取小於等於max的偶數          /// </summary>          /// <param name="max"></param>          /// <returns></returns>          private static IEnumerable<int> GetNumber(int max)          {              for (var i = 0; i <= max; i++)              {                  if (i % 2 == 0)                  {                      // 使用 yield return 返回每次迭代的結果                      yield return i;                  }              }          }            [Fact]          public void T02本地函數()          {              // 通過本地函數獲取結果              var result = GetNumber(10);                // 斷言結果              result.Should().Equal(0, 2, 4, 6, 8, 10);                /**               * 使用本地函數獲取 Enumerable 對象。               * 此處的效果和靜態方法類似。               * 本地函數是 C# 7.0 才開始支持的語法特性。               * 關於本地函數詳細內容可以參見:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/local-functions               */              IEnumerable<int> GetNumber(int max)              {                  for (var i = 0; i <= max; i++)                  {                      if (i % 2 == 0)                      {                          yield return i;                      }                  }              }          }            [Fact]          public void T03分離條件()          {              /**               * 使用 Where 過濾結果               * 這樣的做法分離了“自增”邏輯和“判斷是偶數”的邏輯               */              var result = GetNumber(10)                  .Where(x => x % 2 == 0);                result.Should().Equal(0, 2, 4, 6, 8, 10);                // 迭代時只是進行了自增操作              IEnumerable<int> GetNumber(int max)              {                  for (var i = 0; i <= max; i++)                  {                      yield return i;                  }              }          }            [Fact]          public void T04Linq產生數值()          {              // 系統內置了一個方法來獲取數字的自增序列,因此可以如此簡化              var result = Enumerable                  .Range(0, 11)                  .Where(x => x % 2 == 0);                result.Should().Equal(0, 2, 4, 6, 8, 10);          }            [Fact]          public void T05輸出233()          {              // 輸出3個數字 233              var result = Get233().Take(3);              result.Should().Equal(2, 3, 3);                // 輸出5個數字 23333              result = Get233().Take(5);              result.Should().Equal(2, 3, 3, 3, 3);                IEnumerable<int> Get233()              {                  // 第一次輸出 2                  yield return 2;                  while (true)                  {                      // 後面都輸出 3                      yield return 3;                  }              }                /**               * 值得注意的是 while(true) 並不會導致程序陷入死循環               * 因為 yield return 是採用按需供給的方式執行的。               * 關於這點可以參考右側文章:https://www.cnblogs.com/SilentCode/p/5014068.html               */          }            [Fact]          public void T06獲取10個小於50的隨機數()          {              /**               * 過濾隨機數中小於50的數字,只獲取其中10個結果               */              var result = GetRandom()                  .Where(x => x < 50)                  .Take(10)                  .ToArray();                // 斷言              result.Should().HaveCount(10);              result.All(x => x < 50).Should().BeTrue();                IEnumerable<int> GetRandom()              {                  var random = new Random();                  // 不斷輸出 0-100 的隨機數                  while (true)                  {                      yield return random.Next(0, 100);                  }              }          }            [Fact]          public void T07自動補足隨機數()          {              // 獲取3個數字              var result1 = GetData().Take(3);                // 3 個數字分別為 0,1,2              result1.Should().Equal(0, 1, 2);                // 獲取 10 個數字              var result2 = GetData().Take(10).ToArray();                // 前 5 個數字分別為 0,1,2,3,4              result2.Take(5).Should().Equal(0, 1, 2, 3, 4);              // 從第 5 個開始的數字都大於 10              result2.Skip(5).Take(5).Should().Match(x => x.All(a => a > 10));                /**               * 獲取一組數據,前5個數字是 0,1,2,3,4 。後面繼續獲取則為隨機數               */              IEnumerable<int> GetData()              {                  var staticData = new[] {0, 1, 2, 3, 4};                  foreach (var i in staticData)                  {                      yield return i;                  }                    var random = new Random();                  // 不斷輸出 10-100 的隨機數                  while (true)                  {                      yield return random.Next(10, 100);                  }              }          }            [Fact]          public void T08條件轉循環()          {              var book1 = GetReadingBook(DateTime.Parse("2019-08-30")).First();              book1.Should().Be("每個周五都是快樂的一天");                var book2 = GetReadingBook(DateTime.Parse("2016-02-29")).First();              book2.Should().Be("四年一次的邂逅");                var book3 = GetReadingBook(DateTime.Parse("2019-09-01")).First();              book3.Should().BeSameAs("月老闆軟件開發小妙招三十二則");                // 獲取給定時間需要閱讀的書籍名稱              IEnumerable<string> GetReadingBook(DateTime time)              {                  // 周五                  if (time.DayOfWeek == DayOfWeek.Friday)                  {                      yield return "每個周五都是快樂的一天";                  }                    // 2月29日                  if (time.Date.Month == 2 && time.Date.Day == 29)                  {                      yield return "四年一次的邂逅";                  }                    // 其他時間閱讀名著                  yield return "月老闆軟件開發小妙招三十二則";              }          }            [Fact]          public void T09獲取一個鏈接字符串()          {              // 獲取一個當前可用的鏈接字符串              var conn = GetConnectionString().FirstOrDefault();              conn.Should().Be("Source=賽博坦;UID=月x;Password=******");                IEnumerable<string> GetConnectionString()              {                  // 從一個文件中獲取鏈接字符串                  var connFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "conn.txt");                  if (File.Exists(connFilename))                  {                      var fileContent = File.ReadAllText(connFilename);                      yield return fileContent;                  }                    // 從配置文件中讀取鏈接字符串                  var dbConnectionString = ConfigurationManager.ConnectionStrings["db"]?.ConnectionString;                  if (!string.IsNullOrEmpty(dbConnectionString))                  {                      yield return dbConnectionString;                  }                    // 返回默認的字符串                  yield return "Source=賽博坦;UID=月x;Password=******";              }          }            [Fact]          public async Task T10測試網絡連接()          {              var httpClient = new HttpClient();              try              {                  await Task.WhenAll(SendRequests());                  _testOutputHelper.WriteLine("當前網絡連接正常");              }              catch (Exception e)              {                  _testOutputHelper.WriteLine("當前網絡不正常,請檢查網絡連接");              }                IEnumerable<Task> SendRequests()              {                  yield return Task.Run(() => httpClient.GetAsync("http://www.baidu.com"));                  yield return Task.Run(() => httpClient.GetAsync("http://www.bing.com"));                  yield return Task.Run(() => httpClient.GetAsync("http://www.taobao.com"));              }          }      }  }  

  

源碼說明

以上示例的源代碼放置於博客示例代碼庫中。

項目採用 netcore 2.2 作為目標框架,因此需要安裝 netcore 2.2 SDK 才能運行。