IEnumerable 接口是 C# 开发过程中非常重要的接口,对于其特性和用法的了解是十分必要的。本文将通过6个小例子,来熟悉一下其简单的用法。

<!-- more -->

阅读建议

  • 在阅读本篇时,建议先阅读前篇《试试IEnumerable的10个小例子》,更加助于读者理解。
  • 阅读并理解本篇需要花费5-10分钟左右的时间,而且其中包含一些实践建议。建议先收藏本文,闲时阅读并实践。

全是源码

以下便是这6个小例子,相应的说明均标记在注释中。

每个以 TXX 开头命名的均是一个示例。建议从上往下阅读。

 using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Xunit;
using Xunit.Abstractions; namespace Try_More_On_IEnumerable
{
public class EnumerableTests2
{
private readonly ITestOutputHelper _testOutputHelper; public EnumerableTests2(
ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
} [Fact]
public void T11分组合并()
{
var array1 = new[] {, , , , };
var array2 = new[] {, , , , }; // 通过本地方法合并两个数组为一个数据
var result1 = ConcatArray(array1, array2).ToArray(); // 使用 Linq 中的 Concat 来合并两个 IEnumerable 对象
var result2 = array1.Concat(array2).ToArray(); // 使用 Linq 中的 SelectMany 将 “二维数据” 拉平合并为一个数组
var result3 = new[] {array1, array2}.SelectMany(x => x).ToArray(); /**
* 使用 Enumerable.Range 生成一个数组,这个数据的结果为
* 0,1,2,3,4,5,6,7,8,9
*/
var result = Enumerable.Range(, ).ToArray(); // 通过以上三种方式合并的结果时相同的
result1.Should().Equal(result);
result2.Should().Equal(result);
result3.Should().Equal(result); IEnumerable<T> ConcatArray<T>(IEnumerable<T> source1, IEnumerable<T> source2)
{
foreach (var item in source1)
{
yield return item;
} foreach (var item in source2)
{
yield return item;
}
}
} [Fact]
public void T12拉平三重循环()
{
/**
* 通过本地函数获取 0-999 共 1000 个数字。
* 在 GetSomeData 通过三重循环构造这些数据
* 值得注意的是 GetSomeData 隐藏了三重循环的细节
*/
var result1 = GetSomeData(, , )
.ToArray(); /**
* 与 GetSomeData 方法对比,将“遍历”和“处理”两个逻辑进行了分离。
* “遍历”指的是三重循环本身。
* “处理”指的是三重循环最内部的加法过程。
* 这里通过 Select 方法,将“处理”过程抽离了出来。
* 这其实和 “T03分离条件”中使用 Where 使用的是相同的思想。
*/
var result2 = GetSomeData2(, , )
.Select(tuple => tuple.i * + tuple.j * + tuple.k)
.ToArray(); // 生成一个 0-999 的数组。
var result = Enumerable.Range(, ).ToArray(); result1.Should().Equal(result);
result2.Should().Equal(result); IEnumerable<int> GetSomeData(int maxI, int maxJ, int maxK)
{
for (var i = ; i < maxI; i++)
{
for (var j = ; j < maxJ; j++)
{
for (var k = ; k < maxK; k++)
{
yield return i * + j * + k;
}
}
}
} IEnumerable<(int i, int j, int k)> GetSomeData2(int maxI, int maxJ, int maxK)
{
for (var i = ; i < maxI; i++)
{
for (var j = ; j < maxJ; j++)
{
for (var k = ; k < maxK; k++)
{
yield return (i, j, k);
}
}
}
}
} private class TreeNode
{
public TreeNode()
{
Children = Enumerable.Empty<TreeNode>();
} /// <summary>
/// 当前节点的值
/// </summary>
public int Value { get; set; } /// <summary>
/// 当前节点的子节点列表
/// </summary>
public IEnumerable<TreeNode> Children { get; set; }
} [Fact]
public void T13遍历树()
{
/**
* 树结构如下:
* └─0
* ├─1
* │ └─3
* └─2
*/
var tree = new TreeNode
{
Value = ,
Children = new[]
{
new TreeNode
{
Value = ,
Children = new[]
{
new TreeNode
{
Value =
},
}
},
new TreeNode
{
Value =
},
}
}; // 深度优先遍历的结果
var dftResult = new[] {, , , }; // 通过迭代器实现深度优先遍历
var dft = DFTByEnumerable(tree).ToArray();
dft.Should().Equal(dftResult); // 使用堆栈配合循环算法实现深度优先遍历
var dftList = DFTByStack(tree).ToArray();
dftList.Should().Equal(dftResult); // 递归算法实现深度优先遍历
var dftByRecursion = DFTByRecursion(tree).ToArray();
dftByRecursion.Should().Equal(dftResult); // 广度优先遍历的结果
var bdfResult = new[] {, , , }; /**
* 通过迭代器实现广度优先遍历
* 此处未提供“通过队列配合循环算法”和“递归算法”实现广度优先遍历的两种算法进行对比。读者可以自行尝试。
*/
var bft = BFT(tree).ToArray();
bft.Should().Equal(bdfResult); /**
* 迭代器深度优先遍历
* depth-first traversal
*/
IEnumerable<int> DFTByEnumerable(TreeNode root)
{
yield return root.Value;
foreach (var child in root.Children)
{
foreach (var item in DFTByEnumerable(child))
{
yield return item;
}
}
} // 使用堆栈配合循环算法实现深度优先遍历
IEnumerable<int> DFTByStack(TreeNode root)
{
var result = new List<int>();
var stack = new Stack<TreeNode>();
stack.Push(root);
while (stack.TryPop(out var node))
{
result.Add(node.Value);
foreach (var nodeChild in node.Children.Reverse())
{
stack.Push(nodeChild);
}
} return result;
} // 递归算法实现深度优先遍历
IEnumerable<int> DFTByRecursion(TreeNode root)
{
var list = new List<int> {root.Value};
foreach (var rootChild in root.Children)
{
list.AddRange(DFTByRecursion(rootChild));
} return list;
} // 通过迭代器实现广度优先遍历
IEnumerable<int> BFT(TreeNode root)
{
yield return root.Value; foreach (var bftChild in BFTChildren(root.Children))
{
yield return bftChild;
} IEnumerable<int> BFTChildren(IEnumerable<TreeNode> children)
{
var tempList = new List<TreeNode>();
foreach (var treeNode in children)
{
tempList.Add(treeNode);
yield return treeNode.Value;
} foreach (var bftChild in tempList.SelectMany(treeNode => BFTChildren(treeNode.Children)))
{
yield return bftChild;
}
}
}
} [Fact]
public void T14搜索树()
{
/**
* 此处所指的搜索树是指在遍历树的基础上增加终结遍历的条件。
* 因为一般构建搜索树是为了找到第一个满足条件的数据,因此与单纯的遍历存在不同。
* 树结构如下:
* └─0
* ├─1
* │ └─3
* └─5
* └─2
*/ var tree = new TreeNode
{
Value = ,
Children = new[]
{
new TreeNode
{
Value = ,
Children = new[]
{
new TreeNode
{
Value =
},
}
},
new TreeNode
{
Value = ,
Children = new[]
{
new TreeNode
{
Value =
},
}
},
}
}; /**
* 有了深度优先遍历算法的情况下,再增加一个条件判断,便可以实现深度优先的搜索
* 搜索树中第一个大于等于 3 并且是奇数的数字
*/
var result = DFS(tree, x => x >= && x % == ); /**
* 搜索到的结果是3。
* 特别提出,如果使用广度优先搜索,结果应该是5。
* 读者可以通过 T13遍历树 中的广度优先遍历算法配合 FirstOrDefault 中相同的条件实现。
* 建议读者尝试以上代码尝试一下。
*/
result.Should().Be(); int DFS(TreeNode root, Func<int, bool> predicate)
{
var re = DFTByEnumerable(root)
.FirstOrDefault(predicate);
return re;
} // 迭代器深度优先遍历
IEnumerable<int> DFTByEnumerable(TreeNode root)
{
yield return root.Value;
foreach (var child in root.Children)
{
foreach (var item in DFTByEnumerable(child))
{
yield return item;
}
}
}
} [Fact]
public void T15分页()
{
var arraySource = new[] {, , , , , , , , , }; // 使用迭代器进行分页,每 3 个一页
var enumerablePagedResult = PageByEnumerable(arraySource, ).ToArray(); // 结果一共 4 页
enumerablePagedResult.Should().HaveCount();
// 最后一页只有一个数字,为 9
enumerablePagedResult.Last().Should().Equal(); // 通过常规的 Skip 和 Take 来分页是最为常见的办法。结果应该与上面的分页结果一样
var result3 = NormalPage(arraySource, ).ToArray(); result3.Should().HaveCount();
result3.Last().Should().Equal(); IEnumerable<IEnumerable<int>> PageByEnumerable(IEnumerable<int> source, int pageSize)
{
var onePage = new LinkedList<int>();
foreach (var i in source)
{
onePage.AddLast(i);
if (onePage.Count != pageSize)
{
continue;
} yield return onePage;
onePage = new LinkedList<int>();
} // 最后一页如果数据不足一页,也应该返回该页
if (onePage.Count > )
{
yield return onePage;
}
} IEnumerable<IEnumerable<int>> NormalPage(IReadOnlyCollection<int> source, int pageSize)
{
var pageCount = Math.Ceiling(1.0 * source.Count / pageSize);
for (var i = ; i < pageCount; i++)
{
var offset = i * pageSize;
var onePage = source
.Skip(offset)
.Take(pageSize);
yield return onePage;
}
} /**
* 从写法逻辑上来看,显然 NormalPage 的写法更容易让大众接受
* PageByEnumerable 写法在仅仅只有在一些特殊的情况下才能体现性能上的优势,可读性上却不如 NormalPage
*/
} [Fact]
public void T16分页与多级缓存()
{
/**
* 获取 5 页数据,每页 2 个。
* 依次从 内存、Redis、ElasticSearch和数据库中获取数据。
* 先从内存中获取数据,如果内存中数据不足页,则从 Redis 中获取。
* 若 Redis 获取后还是不足页,进而从 ElasticSearch 中获取。依次类推,直到足页或者再无数据
*/
const int pageSize = ;
const int pageCount = ;
var emptyData = Enumerable.Empty<int>().ToArray(); /**
* 初始化各数据源的数据,除了内存有数据外,其他数据源均没有数据
*/
var memoryData = new[] {, , };
var redisData = emptyData;
var elasticSearchData = emptyData;
var databaseData = emptyData; var result = GetSourceData()
// ToPagination 是一个扩展方法。此处是为了体现链式调用的可读性,转而使用扩展方法,没有使用本地函数
.ToPagination(pageCount, pageSize)
.ToArray(); result.Should().HaveCount();
result[].Should().Equal(, );
result[].Should().Equal(); /**
* 初始化各数据源数据,各个数据源均有一些数据
*/
memoryData = new[] {, , };
redisData = new[] {, , };
elasticSearchData = new[] {, , };
databaseData = Enumerable.Range(, ).ToArray(); var result2 = GetSourceData()
.ToPagination(pageCount, pageSize)
.ToArray(); result2.Should().HaveCount();
result2[].Should().Equal(, );
result2[].Should().Equal(, );
result2[].Should().Equal(, );
result2[].Should().Equal(, );
result2[].Should().Equal(, ); IEnumerable<int> GetSourceData()
{
// 将多数据源的数据连接在一起
var data = GetDataSource()
.SelectMany(x => x);
return data; // 获取数据源
IEnumerable<IEnumerable<int>> GetDataSource()
{
// 将数据源依次返回
yield return GetFromMemory();
yield return GetFromRedis();
yield return GetFromElasticSearch();
yield return GetFromDatabase();
} IEnumerable<int> GetFromMemory()
{
_testOutputHelper.WriteLine("正在从内存中获取数据");
return memoryData;
} IEnumerable<int> GetFromRedis()
{
_testOutputHelper.WriteLine("正在从Redis中获取数据");
return redisData;
} IEnumerable<int> GetFromElasticSearch()
{
_testOutputHelper.WriteLine("正在从ElasticSearch中获取数据");
return elasticSearchData;
} IEnumerable<int> GetFromDatabase()
{
_testOutputHelper.WriteLine("正在从数据库中获取数据");
return databaseData;
}
} /**
* 值得注意的是:
* 由于 Enumerable 按需迭代的特性,如果将 result2 的所属页数改为只获取 1 页。
* 则在执行数据获取时,将不会再控制台中输出从 Redis、ElasticSearch和数据库中获取数据。
* 也就是说,并没有执行这些操作。读者可以自行修改以上代码,加深印象。
*/
}
} public static class EnumerableExtensions
{
/// <summary>
/// 将原数据分页
/// </summary>
/// <param name="source">数据源</param>
/// <param name="pageCount">页数</param>
/// <param name="pageSize">页大小</param>
/// <returns></returns>
public static IEnumerable<IEnumerable<int>> ToPagination(this IEnumerable<int> source,
int pageCount,
int pageSize)
{
var maxCount = pageCount * pageSize;
var countNow = ;
var onePage = new LinkedList<int>();
foreach (var i in source)
{
onePage.AddLast(i);
countNow++; // 如果获取的数量已经达到了分页所需要的总数,则停止进一步迭代
if (countNow == maxCount)
{
break;
} if (onePage.Count != pageSize)
{
continue;
} yield return onePage;
onePage = new LinkedList<int>();
} // 最后一页如果数据不足一页,也应该返回该页
if (onePage.Count > )
{
yield return onePage;
}
}
}
}

  

源码说明

以上示例的源代码放置于博客示例代码库中。

项目采用 netcore 2.2 作为目标框架,因此需要安装 netcore 2.2 SDK 才能运行。

试试 IEnumerable 的另外 6 个小例子的更多相关文章

  1. 试试 IEnumerable 的 10 个小例子

    IEnumerable 接口是 C# 开发过程中非常重要的接口,对于其特性和用法的了解是十分必要的.本文将通过10个小例子,来熟悉一下其简单的用法. 全是源码 以下便是这10个小例子,响应的说明均标记 ...

  2. Runtime的几个小例子(含Demo)

    一.什么是runtime(也就是所谓的“运行时”,因为是在运行时实现的.)           1.runtime是一套底层的c语言API(包括很多强大实用的c语言类型,c语言函数);  [runti ...

  3. 2、Lucene 最简单的使用(小例子)

    在了解了Lucene以后,我打算亲手来做一个Lucene的小例子,这个例子只是Lucene最简单的应用:使用Lucene实现标准的英文搜索: 1.下载Lucene 下载Lucene,到Lucene的官 ...

  4. vuex2.0+两个小例子

    首先vuex概念比较多,一定要搞懂里面的概念,可以参考官网Vuex2.0概念,我写此文的目的是希望能对前端爱好者提供个参考,加深对vuex2.0各核心概念的理解. 废话少说,直接上干货.这是官网上的一 ...

  5. 一个spring boot集成dubbo的小例子

    请移步github,介绍和代码均在上面了:https://github.com/wuxun1997/voicebox 这里再多说两句.github上的这个小例子默认使用组播作为注册中心,你也可以把组播 ...

  6. Vuex2.0边学边记+两个小例子

    最近在研究Vuex2.0,搞了好几天终于有点头绪了. 首先vuex概念比较多,一定要搞懂里面的概念,可以参考官网Vuex2.0概念,我写此文的目的是希望能对前端爱好者提供个参考,加深对vuex2.0各 ...

  7. C#中把任意类型的泛型集合转换成SQLXML数据格式的小例子

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.D ...

  8. python2.7练习小例子(十)

        10):古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?     程序分析:兔子的规律为数列1,1 ...

  9. springmvc入门的第一个小例子

    今天我们探讨一下springmvc,由于是初学,所以简单的了解一下 springmvc的流程,后续会持续更新... 由一个小例子来简单的了解一下 springmvc springmvc是spring框 ...

随机推荐

  1. 改 Anaconda Jupyter Notebook 开发文件保存目录

    1.打开cmd,输入命令找到配置文件路径 jupyter notebook --generate-config 2.打开 jupyter_notebook_config.py 修改配置 c.Noteb ...

  2. 高性能MySQL之基础架构

    一.背景 为什么我们需要先学习MYSQL的基础架构先呢? 原因很简单,当我们需要了解一件事物的时候,我们只有站在宏观的层面,才能层层剥丝抽茧的去理解问题.举个例子,我们要看一个框架的源码,一开始就想进 ...

  3. go 学习笔记之有意思的变量和不安分的常量

    首先希望学习 Go 语言的爱好者至少拥有其他语言的编程经验,如果是完全零基础的小白用户,本教程可能并不适合阅读或尝试阅读看看,系列笔记的目标是站在其他语言的角度学习新的语言,理解 Go 语言,进而写出 ...

  4. springboot集成redis实现消息发布订阅模式-双通道(跨多服务器)

    基础配置参考https://blog.csdn.net/llll234/article/details/80966952 查看了基础配置那么会遇到一下几个问题: 1.实际应用中可能会订阅多个通道,而一 ...

  5. windows server2012 nVME和网卡等驱动和不识别RAID10问题

    安装2012---不识别M.2 nVME,下官方驱动,注入到系统里 缺多驱动---用ITSK万能驱动添加:|Win8012R2.x64(可解决不支持操作系统,win10与server2012R2通用) ...

  6. SAP无法激活表问题

    因为修改了表结构导致无法激活,刚开始以为是数据库没有调整,然后试着运行SE14,发现还是报错 这个时候就要看看数据库服务器时候正常,输入事务码ST04,查看概览,发现磁盘已满 登录HANA Studi ...

  7. tensorflow学习笔记——图像数据处理

    喜欢摄影的盆友都知道图像的亮度,对比度等属性对图像的影响是非常大的,相同物体在不同亮度,对比度下差别非常大.然而在很多图像识别问题中,这些因素都不应该影响最后的结果.所以本文将学习如何对图像数据进行预 ...

  8. Salesforce LWC学习(四) 父子component交互 / component声明周期管理 / 事件处理

    我们在上篇介绍了 @track / @api的区别.在父子 component中,针对api类型的变量,如果声明以后就只允许在parent修改,son component修改便会导致报错. sonIt ...

  9. Jenkins使用aqua-microscanner-plugin进行容器漏洞扫描

    官方地址:https://github.com/jenkinsci/aqua-microscanner-plugin Step1 在jenkins安装"Aqua MicroScanner&q ...

  10. Java面向对象特性总结

    1.面对对象与面对过程的区别 什么是封装?我看到过这样一个例子: 我要用洗衣机洗衣服,只需要按一下开关和洗涤模式就可以了.有必要了解洗衣机内 部的结构吗?有必要碰电动机吗?有必要了解如何通电的吗? 如 ...