c#入门-Linq其他注意事项

Linq只保存操作

延迟执行

由Linq方法返回的IEnumerable接口,是一个藏起来的类型。
没有源码没法访问这个类型。这个类型里面记录了你的操作,但不会执行
通过这个类型继续调用Linq方法,会把接下来的操作接在后面。

这些操作直到使用的时候,才一并执行
操作包括foreach,和一些不返回Linq的方法,例如Max,All,Count,ToArray。
例如:

int[] p = { 1, 2, 3, 4, 5 };
var e = from a in p select a;

foreach (var item in e)
{
	Console.WriteLine(item);
}
int[] p = { 1, 2, 3, 4, 5 };
var e = from a in p select a;

for (int i = 0; i < p.Length; i++)
{
	p[i] = 0;
}

foreach (var item in e)
{
	Console.WriteLine(item);
}

多次执行

基于上述原理,如果你多次执行Linq,那么foreach会执行多次。

class Foo : IEnumerable<int>
{
	public IEnumerator<int> GetEnumerator()
	{
		Console.WriteLine("你执行了一次查询");
		for (int i = 0; i < 20; i++)
		{
			yield return i;
		}
        Console.WriteLine("查询结束");
	}
	IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Foo f = new Foo();
var t = f.Select(s => s * s);
 
foreach (var item in t)
{
	Console.WriteLine(item);
}  
Console.WriteLine(t.Count());
Console.WriteLine(t.Max());//运行这段代码会出现三次"你执行了一次查询"。

所以如果Linq只使用一次,那么直接使用Linq。这样不会创建容器。
数组等容器创建起来都是占空间的。而Linq只占一个迭代器的空间。

而如果多次使用一个查询,那就应该把查询结果存起来。

Foo f = new Foo();
var t = f.Select(s => s * s).ToArray();
 
foreach (var item in t)
{
	Console.WriteLine(item);
}  
Console.WriteLine(t.Count());
Console.WriteLine(t.Max());//运行这段代码只会出现一次"你执行了一次查询"。

不要在Linq中间转容器

如果你保存的遍历是一个Linq,而这个Linq的操作中间有转容器操作,
那问题更大了。他会执行全程,而不是从转容器到最后的Linq之间。

var t = f.Select(s => s * s).ToArray().Reverse();

不仅如此,他还会每次执行都创建一个容器,消耗大量内存。
而且还要往容器里添加内容,最后转为对容器调用Linq,消耗大量时间。

不要用for循环操作Linq方法

在for循环中执行Linq方法将是灾难性的。

Foo f = new Foo();
var t = f.Select(s => s * s) ;

for (int i = 0; i < t.Count(); i++)
{
    Console.WriteLine(t.ElementAt(i));
}

不要使用Skip和Take在for循环中截取元素

Foo f = new Foo();
var t = f.Select(s => s).ToArray();

for (int i = 0; i < t.Length / 3; i++)
{
	var b = t.Skip(i * 3).Take(3);
	foreach (var item in b)
	{
		Console.WriteLine(item);
	}
	Console.WriteLine();
}

Skip也要从头开始访问元素,而不是直接跳过。
所以应该改为数组或其他可以截取的容器进行访问。

你可以自定义Linq扩展方法

public static class Extend
{
	public static IEnumerable<T> Even<T>(this IEnumerable<T> enumber)
	{
		bool b = false;
		foreach (var item in enumber)
		{
			if (b)
				yield return item;
			b ^= true;
		}
	}
}

返回值是IEnumerable的方法也可以使用yield语法。
编译器会自动帮你合成出一个记录操作的IEnumerable,而不当场执行。

Linq只会从头开始遍历

由于迭代器是只会从头开始遍历的。所以Linq也只能从头开始遍历。
Linq内的Reverse方法也要在遍历全部元素后才能反转。
更不必说排序这种必须完全遍历才能执行的方法。

但是,如果一个方法只需要从头开始找到需要的值,那么不需要遍历完全程。
例如All,Any,FirstOrDefault,ElementAtOrDefault,Skip,Take方法。
他们找到自己感兴趣的值就会走。

而基于末尾和倒数的方法不行。例如LastOrDefault,TakeLast。

被过滤的元素不会执行后续操作

执行了筛选或截取操作,如Where,Skip,Take后不满足条件的元素不会再执行后续内容。
因此如果有这些操作,能往前摆的就往前摆。

有多个Where,SkipWhere,TakeWhere时,应该同时考虑能过滤的元素比例,和执行时间。
例如方法A可以过滤90%的元素,需要花费4的时间。
方法B可以过滤50%的元素,需要花费1的时间。
先A后B需要:4*100%+10%*1=410%
先B后A需要:1*100%+50%*4=300%

计算方法是:A发起比较,目标B
A的过滤比例*B的执行时间比较B的过滤比例*A的执行时间
90%*1比较50%*4
90%对比200%
B能节省更多时间,所以把B放在前面

选用合适的Linq方法

有多个条件的拆分,可以尝试使用分组

Foo f = new Foo();
var t = f.Where(s => s % 2 == 0).ToArray();
var t2 = f.Where(s => s % 3 == 0).ToArray();
var t3 = f.Where(s => s % 6 == 0).ToArray();

如果后续只需要对元素进行查询,可以使用ToLookup

var look = f.ToLookup(s => (s % 2 == 0, s % 3 == 0));
Console.WriteLine(look[(true, false)].Max());

否则,使用分组,并映射成容器。

var arr = f.GroupBy(s => (s % 2 == 0, s % 3 == 0)). ToDictionary(s=>s.Key,s=>s.ToArray());
Console.WriteLine(arr[(false,true)].Length);

使用联表找对应元素,而不要多次使用first查找元素

		string[] arr1 = null;
		string[] arr2 = null;

		foreach (var item in arr1)
		{
			if (arr2.FirstOrDefault(s => s == item) is { } temp)
			{
				//操作找出来的数据
			}
		}
		string[] arr1 = null;
		string[] arr2 = null;

		var t = arr1.Join(arr2, a => a, b => b, (a, b) => (a, b));
		foreach (var item in t)
		{
			//操作item
		}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值