CH15 支持标准查询操作符的集合接口
15.1集合初始化器
1.知识点
1.集合(Collection)类是专门用于数据存储和检索的类I
2.ICollection<T>和IEnumerable<T>区别:ICollection<T>实现了IEnumerable<T>和 IEnumerable,并且支持Add(),Clear()等操作。
3.IQueryable<T>和IEnumerable<T>几乎完全一样,IQueryable<T>是从IEnumerable<T>派生出来的,但是没有继承IEnumerable<T>的扩展方法,区别:IQueryable<T>实现了Linq Provider这样可以使表达式可以转换成另一种语言,例如C#可以转换成SQL,只需要通过编写表达式(而不是SQL)就能对完成对数据库的访问。
4.大多数集合只实现IEnumerable<T>而没有实现ICollection<T>
15.2 IEnumerable<T>使类成为集合
1.Foreach与Enumerator关系探究
现在,我们初始化一个栈
var stack = new Stack<int>();
假如我们调用foreach
foreach (var item in stack)
{
Console.WriteLine("任务2输出"+item);
}
foreach这个关键字在单线程任务结果其实这个等价于先获取stack的迭代器,然后调用MoveNext方法,最后记录当前迭代器的current数值。换句话说上面的代码等价于下面代码
var enumerator = stack.GetEnumerator();
while (enumerator.MoveNext())
{
number = enumerator.Current;
Console.WriteLine("任务2输出"+number);
}
那么在多线程的情况下是怎么样的?
static void Main(string[] args)
{
int number;
var stack = new Stack<int>();
for (int i = 1; i < 50; i++)
{
stack.Push(i);
}
Console.WriteLine("第一次测试开始");
var enumerator = stack.GetEnumerator();
Task task1 = Task.Run(() =>
{
while (enumerator.MoveNext())
{
number = enumerator.Current;
Console.WriteLine("任务1输出"+number);
}
});
while (enumerator.MoveNext())
{
number = enumerator.Current;
Console.WriteLine("主任务输出"+number);
}
task1.Wait();
Console.WriteLine("第一次测试结束");
Task task2 = Task.Run(() =>
{
foreach (var item in stack)
{
Console.WriteLine("任务2输出"+item);
}
});
foreach (var item in stack)
{
Console.WriteLine("主任务输出"+item);
}
task2.Wait();
Console.WriteLine("第二次测试结束");
}
第一次输出的结果如下
可以发现任务1和主任务并不是独立输出的,它们共同输出了一个栈。因为它们共用了一个enumerator,由于线程之间执行情况的不确定性,每个线程会读取当前的迭代器current,导致结果输出的不确定性。
第二次输出
第二次输出,虽然两个任务异步进行,但是每个任务都能对应正确输出一个栈。因为每个foreach中都对应一个enumerator,并且在推出循环的时候调用Dispose()方法。
15.3 标准查询操作符
1.知识点
常用查询操作以前写过一次地址:https://lmsniubi.blog.csdn.net/article/details/106453813
2.Lambda表达式的延迟查询
IEnumerable<People> peoples = new List<People>() {new People {UserId="123"}, new People { UserId = "124" }, new People { UserId = "125" }, new People { UserId = "126" } };
bool result=false;
peoples = peoples.Where(p =>
{
if (p.UserId == "123")
{
result = true;
Console.WriteLine(p.UserId);
}
return result;
});
Console.WriteLine("1.end");
foreach (var item in peoples)
{
}
Console.WriteLine();
Console.WriteLine(peoples.Count());
Console.WriteLine("2.end");
Console.WriteLine();
peoples = peoples.ToArray();
Console.WriteLine(peoples.Count());
Console.WriteLine("3.end");
输出结果
输出结果1.end 明明是在 where语句后写的,但是却提前输出,这不符合我们的逻辑顺序。原因是因为Lambda表达式的推迟执行,意思就是Lambda在声明的时候不执行,直到它被使用的那一刻才执行。
Count()和ToArrary()方法在内部都会使用foreach 触发Lambda表达式的执行,所以输出的结果符合逻辑顺序。
3.设计规范
-
慎用AsParallel(),并行执行代价是可能引入竞态条件,一个线程上的一个操作可能会与一个不同线程上的一个操作混合造成数据被破坏。
-
检查集合是否有项目的时候使用Any()方法而不调用Count(),如果集合存在Count属性就不用调用Count()方法
-
要避免反复执行,一个查询在执行之后,有必要把获取的数据缓存起来,可以使用Toxxx方法将起赋值给一个缓存的集合,避免无所谓的遍历。
-
不要在OrderBy()调用的基础上再调用OrderBy(),而是使用ThenBy(),因为重复调用OrderBy()会撤销上一个OrderBy()的工作。但OrderBy()/OrderByDescending(),ThenBy()/ThenByDescending()升降序方法混用没有问题,但是进一步排序就要使用ThenBy()调用(无论升序还是降序)