c#笔记-迭代器

foreach循环

一些类型可以使用foreach循环来遍历他的所有元素。

int[] arr = {
    
     1, 2, 3, 4, 5 };
string str = "hello";
foreach (var item in arr)
{
    
    
	Console.WriteLine(item);
}
foreach (var item in str)
{
    
    
	Console.WriteLine(item);
}

foreach循环也是循环,也可以使用breakcontinue这样的跳转语句。
可以被foreach类型的共同特征是,他们实现了IEnumerable接口或他的泛型版本。

实际上只是想使用foreach的话,只要有公开同名方法就可以,甚至扩展方法都可以。
不需要一定实现这个接口。如果要遍历引用变量的时候,IEnumerable泛型版是写不出来的。

迭代器

able的意思是可以做某件事。IEnumerable就是可以进行迭代的意思。
这个接口里只有一个方法,返回一个IEnumerator接口(迭代器)。

不同的数据类型可能有不同的遍历方式。例如二维数组使用for循环需要这样遍历。

int[,] arr2 = new int[3, 6];
for (int i = 0; i < arr2.GetLength(0); i++)
{
    
    
	for (int j = 0; j < arr2.GetLength(1); j++)
	{
    
    
		arr2[i, j] = i * 10 + j;
	}
}
foreach (var item in arr2)
{
    
    
	Console.WriteLine(item);
}

实现了可迭代的类型,可以使用foreach循环来遍历所有元素。
无论它for循环版本长成什么样,foreach循环都是相同的写法。

迭代器原理

IEnumerable会获得一个迭代器。
每次进行foreach的时候,编译器都会调用这个方法来获得一个新的迭代器。
如果你自己调用这个方法,获得IEnumerator接口,你可以主动控制他的遍历。

IEnumerator接口内有4个成员

  • 当前值(属性)
  • 是否存在下一个值(方法)
  • 释放资源(方法)
  • 重置(方法)

其实实现这个接口就是告诉编译器,如何以while循环方式对自己进行遍历。
一个foreach循环实际上进行了如下操作

IEnumerator<char> hello = "hello".GetEnumerator();
while (hello.MoveNext())
{
    
    
	Console.WriteLine(hello.Current);
}
hello.Dispose();

这里面是没有使用Reset(重置)方法的。
这个方法只是用来给你手动调用,避免反复获取迭代器。

迭代器语法

如果一个方法的返回值是IEnumerator或他的泛型版本,
那么在这个方法中可以使用yield returnyield break
编译器会根据方法里的内容自动合成出一个迭代器。

yield return用来返回一个单独的元素。
如果方法能够执行到这里,那么迭代器的MoveNext会返回true
并且Current属性会是yield return返回的值。
如果没有遇到yield return却结束了这个方法,那么MoveNext返回false

yield break用来终止方法。是在迭代器语法中,return的替代品。
在迭代器方法中,yield方式依次返回值,和return返回整个迭代器,
只能选择其中一种形式。即便用流程控制语句声明一个始终无法访问到的yield
也不能再使用return了。

static class Extend  
{
    
    
	public static IEnumerator<bool> GetEnumerator(this byte b) {
    
    
		for (int i = 0; i < 8; i++)
		{
    
    
			yield return (b & 1) == 1;
			b >>= 1;
		}
		yield break;
	}
}
byte b = 56;
foreach (var item in b)
{
    
    
	Console.WriteLine(item);
} 

自定义迭代器

foreach中的临时变量是只读的。因为IEnumeratorCurrent属性只有get访问器。
但如果自定义一个迭代器,返回引用变量,那么foreach中的迭代变量可以进行赋值。

和可迭代类型类似,迭代器类型也只要求具有一些可以访问的成员,就是一个迭代器。
自定义迭代器只要求包含Current属性和MoveNext方法。
Current属性的类型就是foreach中迭代变量自动识别的类型。

class MyEnumerable
{
    
    
	//一个用来被遍历的东西
	public readonly int[] Arr;
	public MyEnumerable(int length)
	{
    
    
		Arr = new int[length];
	}

	//获取迭代器
	public MyEnumerator GetEnumerator()
	{
    
    
		return new MyEnumerator(this);
	}

	//因为这个迭代器只能用在自己类型身上,所以作为内部类
	public struct MyEnumerator
	{
    
    
		//自备索引
		private int index = -1;

		//返回当前索引的引用变量
		public ref int Current => ref Enumerable.Arr[index];

		//判断是否还能有下一个值
		public bool MoveNext()
		{
    
    
			return ++index < Enumerable.Arr.Length;
		}

		//储存需要迭代的东西
		private MyEnumerable Enumerable;
		public MyEnumerator(MyEnumerable enumerable)
		{
    
    
			if (enumerable == null)
			{
    
    
				throw new ArgumentNullException("null没有东西可以迭代");
			}
			Enumerable = enumerable;
		}
	}
}

这样,就创建了一个具有自定义迭代器的类型。
并且他的迭代变量是引用变量,可以修改。

MyEnumerable my = new MyEnumerable(8);
int i = 10;
foreach (ref var item in my)
{
    
    
	item = i++;
}
foreach (var item in my.Arr)
{
    
    
    Console.WriteLine(item);
}

Linq

Language-Integrated Query是指一种将查询能力直接集成到编程语言中的技术。
查询能力是指可以使用统一的方式在编程语言中对不同的数据进行检索、过滤、排序、分组、聚合等操作的能力。
而迭代器的统一方式遍历延迟执行按需返回就是统一查询能力的基础。
C# LINQ是C#语言实现Linq技术的一种方式,它提供了查询语法和方法语法两种编写查询的方式。
c#中的Linq方法都是IEnumerable的扩展方法,或者是能返回IEnumerable的方法。

查询功能的一种常见的分类是按照运算符的功能进行分类

  • 映射运算符:对序列中的元素进行转换或投影,例如 select,select many 等。
  • 筛选运算符:对序列中的元素进行条件判断或筛选,例如 where,of type,any,all 等。
  • 截取运算符:对序列中的元素进行截取或跳过,例如 take,skip,take while,skip while 等。
  • 聚合运算符:对序列中的元素进行数学运算或分组,例如 sum,average,count,group by 等。
  • 排序运算符:对序列中的元素进行排序或反转,例如 order by,then by,reverse 等。
  • 连接运算符:对两个或多个序列进行连接或合并,例如 join,group join,concat 等。
  • 转换运算符:将一个序列转换为另一种类型的集合,例如 to list,to array,to dictionary 等。
  • 元素运算符:从序列中获取单个元素或默认值,例如 first,last,single,element at 等。
  • 集合运算符:对两个序列进行集合操作,例如 union,intersect,except 等。

查询表达式

查询语法在编译后也会转为对扩展方法的调用,并且标准查询语法没有完全涵盖c#所有Linq方法。
这里只做一个简单的示例。假如具有以下类定义

class Student
{
    
    
	public int Id;
	public string? Name;
	public int Age;
}

并具有以下初始数据

Student[] stArr = new Student[]
{
    
    
		new Student {
    
     Id = 1, Name = "张三", Age = 18 },
		new Student {
    
     Id = 2, Name = "李四", Age = 19 },
		new Student {
    
     Id = 3, Name = "王五", Age = 17 }, 
		new Student {
    
     Id = 4, Name = "赵六", Age = 20 },
		new Student {
    
     Id = 5, Name = "孙七", Age = 18 },
		new Student {
    
     Id = 6, Name = "周八", Age = 19 } 
};

一段查询表达式示例如下

IEnumerable<int>? age = from student in stArr
						where student.Id % 2 == 0
						select student.Age;

student是这个表达式中的临时变量。
它表示把stArr中遍历出来的所有元素称作student
in stArr表示从数据源(实现IEnumerable接口)stArr遍历元素。
where student.Id % 2 == 0表示对遍历元素进行筛选。只保留那些Id是偶数的元素。
select表示进行一个映射。获取筛选后的元素的Age字段。

延迟执行

在使用Linq直接获得到一个IEnumerable后可以对这个遍历进行foreach的遍历或其他操作。
但是,IEnumerable只保存操作不保存实际数据,并且只有在被使用时才会执行操作。

例如,以上述方式获取到了可迭代接口后,对数据源做出了修改

foreach (var item in age)
{
    
    
    Console.WriteLine(item);
}
Console.WriteLine("================");
foreach (var item in stArr)
{
    
    
	item.Id *= 2;
	item.Age += 10;
}
foreach (var item in age)
{
    
    
	Console.WriteLine(item);
}

输出如下

19
20
19
================
28
29
27
30
28
29

Linq方法版本

链式调用是一种编程模式,它允许一个类型调用了方法后,
返回值也可以继续调用方法,最终构成一长串方法的调用。
如果一个类型调用的方法返回值就是自己类型,那么就可以轻易编写出链式调用。
多数Linq方法的返回值是可迭代类型。因此复杂的linq能构造出一个非常长的长串。

IEnumerable<string?> name = stArr
	.Where(st => st.Age >= 18)
	.Select(st => st.Name);

对这个可迭代类型进行foreach会按照你的链接顺序依次执行。
也就是说把Where等筛选元素的操作放在开头会优化一些性能。

linq的方法版本都是基于委托参数的。因为编写linq的时候不知道你会调用什么方法。
使用委托可以自定义你需要的操作,把操作作为参数传入到方法内进行调用。

详细的方法列表可以参阅这里

非延时运算

一些查询操作例如聚合,转换,集合等操作,他们不返回一个可迭代类型,而是直接返回单个值。
这种情况下,这之前的链接的方法会一并被调用。

但是,如果聚合的值还不是最终值,聚合出来的值只是一个中间遍历并且也是一个序列。
对这个序列继续链接linq方法。那么这些中间变量,每次访问时都会被创建。
例如这个中间变量是一个数组,那么之后每次访问这个可迭代类型时,都会创建一个数组。

如果只调用一次,那么建议把这个linq优化成不需要这个数组的形式。
如果需要调用多次,那么建议保存下来这个数组,直接从这个数组上进行linq操作。

短路运算

linq操作是通过迭代器接口,从前往后遍历所有值的。
一些操作基于顺序并且不需要访问完整的序列,例如查找第一个满足条件的值。

但是,如果在这之前插入了任何需要遍历完整序列的操作,
例如排序,反转序列,那么这个序列就会被完全遍历。

因此,使用linq的时候应该仔细思考他们的顺序来优化linq。
例如排序应该尽可能靠后,筛选过滤应该尽可能靠前。
如果使用了短路运算,思考能不能不加入需要完整遍历序列的操作。

猜你喜欢

转载自blog.csdn.net/zms9110750/article/details/130723432