C#迭代器与LINQ查询操作符

简单实例:

static void Main(string[] args) { foreach (var number in EvenSequence(1,5)) { Console.WriteLine(number); } }public static IEnumerable EvenSequence(int firstNumber,int lastNumber) { for (int number = firstNumber ; number <= lastNumber; number++) { if (number % 2 == 0) { yield return number; } } }

迭代器方法或 get 访问器可对集合执行自定义迭代。 迭代器方法使用 yield return 语句返回元素,每次返回一个。 到达 yield return 语句时,会记住当前在代码中的位置。 下次调用迭代器函数时,将从该位置重新开始执行。
手动实现一个繁琐的迭代器:
假设实现一个基于环形缓冲的集合类型,实现IEbunmerable接口,用户可以获得集合中全部元素。
我们提供一个设置值和起始点的构造函数
object[] values = { "a", "b", "c" }; IterationSample collection = new IterationSample(values, 1); foreach (object col in collection) { Console.WriteLine(col); }

IterationSample类继承IEnumerable的实现
class IterationSample : IEnumerable { public Object[] values; public Int32 startingPoint; public IterationSample(Object[] values,Int32 startingPoint) { this.values = values; this.startingPoint = startingPoint; } public IEnumerator GetEnumerator() { throw new NotImplementedException(); } }

迭代器不是一次返回全部数据,而是一次请求一个数据,因此,我们需要记录客户请求到了集合的哪个记录。我们创建另一个类来实现迭代器本身,来保证GetEbumerator迭代对象的独立
class IterationSampleEnumerator : IEnumerator { IterationSample parent; Int32 position; internal IterationSampleEnumerator(IterationSample parent) { this.parent = parent; position = -1; } public bool MoveNext() { if (position != parent.values.Length) { position++; } return position < parent.values.Length; } public object Current { get { if (position == -1 || position == parent.values.Length) { throw new InvalidOperationException(); } Int32 index = position + parent.startingPoint; index = index % parent.values.Length; return parent.values[index]; } } public void Reset() { position = -1; } }

迭代器:记录迭代的原始集合,记录当前游标,根据当前游标,和数组定义的起始位置设置迭代器在数组中的位置。初始化将当前迭代器设定在第一个元素之前,第一次调用迭代器首先调用MoveNext,然后调用Current.游标自增时进行条件判断。
IterationSimple类中的GetEnumerator中返回迭代类。
【C#迭代器与LINQ查询操作符】通过yield语句进行简化迭代
public IEnumerator GetEnumerator() { for (int index = 0; index < this.values.Length; index++) { yield return values[(index + startingPoint) % values.Length]; } }

上面的语句简化了手动实现IEnumerator的过程,这条语句告诉编译器这不是一个简单的方法,而是执行一个迭代块(yield block),它返回一个IEnumerator对象,你能够使用迭代块来执行迭代方法并返回一个IEnumerator需要实现的类型。yield return 语句必须返回和块的最后的返回类型兼容的类型。迭代块实际上是让编译器为我们创建了一个状态机,它创建了一个实现状态机的内部类。这个类记住了我们迭代器的准确当前位置以及本地变量,包括参数。
如下的代码,展示了迭代器的执行流程
class Program { static readonly String Padding = new String(' ', 30); static IEnumerable CreateEnumerable() { Console.WriteLine("{0} CreateEnumerable()方法开始", Padding); for (int i = 0; i < 3; i++) { Console.WriteLine("{0}开始 yield {1}", i); yield return i; Console.WriteLine("{0}yield 结束", Padding); } Console.WriteLine("{0} Yielding最后一个值", Padding); yield return -1; Console.WriteLine("{0} CreateEnumerable()方法结束", Padding); }static void Main(string[] args) { IEnumerable iterable = CreateEnumerable(); IEnumerator iterator = iterable.GetEnumerator(); Console.WriteLine("开始迭代"); while (true) { Console.WriteLine("调用MoveNext方法……"); Boolean result = iterator.MoveNext(); Console.WriteLine("MoveNext方法返回的{0}", result); if (!result) { break; } Console.WriteLine("获取当前值……"); Console.WriteLine("获取到的当前值为{0}", iterator.Current); } Console.ReadKey(); } }

如果只是想从一个方法中返回一些数据,那么使用IEnumerable。在Main中
IEnumerable iterable = CreateEnumerable();

这里是一个语法糖,CreateEnumerable直接返回一个继承IEnumerable的实例。
从迭代快调用顺序中可以看出:
  • 直到第一次调用MoveNext,实际方法才会被调用
  • 在调用MoveNext的时候,已经做好了所有操作,返回Current属性并没有执行任何代码
yield break 结束一个迭代
他能够马上结束迭代,使得下一次调用MoveNext返回false
参考资料:https://www.cnblogs.com/yangecnu/archive/2012/03/17/2402432.html
(1)使用
针对集合类型编写foreach代码块,都是在使用迭代器
集合类型实现了IEnumerable接口
都有一个GetEnumerator方法
(2)迭代器优点
假如要遍历一个庞大的集合,只要其中一个元素满足条件,据完成了任务。
(3)yield关键字
MSDN中:
在迭代器块中用于向枚举数对象提供值或发出迭代结束信号。
(4)注意事项:
1.在foreach循环式多考虑线程安全性,在foreach时不要试图对便利的集合进行remove和add操作,任何集合,即使被标记为线程安全,在foreach时,增加项和移除项都会导致异常。
2.IEnumerable接口是LINQ特性的核心接口
只有实现了IEnumerable接口的集合,才能执行相关的LINQ操作,比如select,where等
LINQ
1.查询操作符
(1)源起
.net的设计者在类库中定义了一系列拓展的方法,方便用户操作集合对象。
(2)使用
一些系列拓展方法,eg:Where,Max,Select,Sum,Any,Average,All,Concat等都是针对IEnumerable的对象进行拓展,
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace _1.LINQLearn { class Program { static void Main(string[] args) { List ltemp = new List { 1, 23, 4, 5, 6 }; var res = ltemp .Where(a => { return a > 4; }) .Sum(); Console.WriteLine(res); Console.ReadKey(); } } }

<1>上面的代码中用了两个拓展方法。
Where拓展方法,需要传入一个Func类型的泛型委托,此泛型委托,需要一个int的输入参数和一个bool类型的返回值。
<2>Sum拓展方法计算了Where拓展方法返回的集合的和
(3)好处
上面的代码中
ltemp
.Where(a => { return a > 4; })
.Sum();
上面的一句可以完全写成
(from v in arr where v > 3 select v).Sum();
(4)标准查询操作符说明
<1>过滤
Where
用法:arr.Where(a=>{ return a>3; })
说明:找到集合中满足指定条件的元素
OfType
用法:arr.OfType()
说明:根据类型,筛选集合中的元素
<2>投影
Select
用法:arr.Select(a=>a.ToString());
说明:将集合中的每个元素投影到进的集合中。上例子中新集合是一个IEnumerable的集合
SelectMany
用法arr.SelectMany(a=>{return new List(){a.Tostring(); }};
查询表达式
(1)源起
查询早做副表示扩张方法来操作及和,虽然已经比较方便,但可读性和代码的语义来考虑,仍有不足,于是产生了查询表达式的写法。虽然很像SQL,但是用本质不同
(2)用法
from v in arr where v>3 select v
(3)说明:
... ...
参考文档:
https://blog.csdn.net/snakorse/article/details/44171295

    推荐阅读