微软在.NET 3.5中加入了LINQ技术,作为配套改进了C#语言,加入了Lambda表达式,扩展方法,匿名类型等新特性用以支持LINQ。微软同时提出了要使用声明式编程,即描述计算规则,而不是描述计算过程。

使用LINQ技术能很好地做到声明式编程,写出的代码表意能力强,可读性高,避免了以往或其他语言的代码中充斥大量表意不明的for循环甚至多层循环的问题。不要小看for循环和Where,Select,OrderBy等扩展方法的区别,可以不通过注释一眼就能看出代码意图真的很重要。当看到Java代码中一大堆的for循环,包括多层循环,又没有注释,必须仔细看才能了解代码作用时,真的很头大。个人认为LINQ是C#语言区别于其他语言的最显著的特性,也是最大的优势之一。

当然现在大多数主流语言都加入了Lambda表达式,从而可以使用类似于LINQ的技术,达到声明式编程。比如Java语言在Java 8中加入了和C#几乎一样的Lambda表达式语法,并加入了Stream API,以达到类似于LINQ的用法。

如此可见,声明式编程是发展趋势,既然使用C#,就要多用LINQ,用好LINQ,用对LINQ。不要再写一堆一堆的for循环了!

要用好LINQ,就要学好LINQ,理解其原理,机制和用法。推荐一个学习和研究LINQ的好工具LINQPad,下面是官网和官网上的截图。

http://www.linqpad.net/

下面针对几个关键点,对LINQ进行一些初步研究。有些问题可能是使用LINQ多年的人都理解得不对的。

首先看下面的程序。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LinqResearch
{
class Program
{
static void Main(string[] args)
{
var list = new List<int> { 2, 1, 6, 4, 3, 5, 7, 8, 10, 9 };
Console.WriteLine("list");
var list1 = list.Select(i =>
{
Console.WriteLine("In select {0}", i);
return i * i;
});
Console.WriteLine("list1");
var list2 = list1.Where(i =>
{
Console.WriteLine("In where>10 {0}", i);
return i > 10;
});
Console.WriteLine("list2");
var list3 = list2.Where(i =>
{
Console.WriteLine("In where<60 {0}", i);
return i < 60;
});
Console.WriteLine("list3");
var list4 = list3.OrderBy(i =>
{
Console.WriteLine("In orderby {0}", i);
return i;
});
Console.WriteLine("list4");
var list5 = list4.ToList();
Console.WriteLine("list5");
foreach (var i in list5)
{
Console.WriteLine(i);
}
}
}
}

先不要看下面的运行结果,想想打印出的是什么,然后再看结果,看看和想的一样吗?

list
list1
list2
list3
list4
In select 2
In where>10 4
In select 1
In where>10 1
In select 6
In where>10 36
In where<60 36
In select 4
In where>10 16
In where<60 16
In select 3
In where>10 9
In select 5
In where>10 25
In where<60 25
In select 7
In where>10 49
In where<60 49
In select 8
In where>10 64
In where<60 64
In select 10
In where>10 100
In where<60 100
In select 9
In where>10 81
In where<60 81
In orderby 36
In orderby 16
In orderby 25
In orderby 49
list5
16
25
36
49

为什么先打印出list 到 list4,而没有进到Lambda里面?

这是因为LINQ是延时计算的,即只有foreach或ToList时才去做真正计算,前面的Select,Where等语句只是声明了计算规则,而不进行计算。

这点很重要,如果不明白这点,就会写出有BUG的代码,如下面的程序,打印出的是1和2,而不是1。

            var a = 2;
var list = new List<int> { 1, 2, 3 };
var list1 = list.Where(i => i < a);
a = 3;
foreach (var i in list1)
{
Console.WriteLine(i);
}

后面打印出的为什么先是select和where交错,然后是orderby,而不是先select再where,最后orderby?

这时因为Select,Where等这些扩展方法,在声明计算规则时是有优化的(内部可能通过表达式树等方法实现),它并不是傻傻的按照原始定义的规则,顺序执行,而是以一种优化的方法计算并获得结果。所以使用 LINQ一般会比自己写的原始的一大堆for循环性能还高,除非花大量时间优化自己的逻辑(一般不会有这个时间)。

可以看到针对元素2和1,并没有打印出In where<60 的行,这说明针对这两个元素,第二个Where里的代码并没有执行,因为第一个Where都没有通过。在进行完投影(Select)和筛选(Where)后,最后进行排序(OrderBy),只针对筛选后留下的元素执行OrderBy里面的计算逻辑,一点也不浪费。

上面的程序有人可能会写成这样。

            var list = new List<int> { 2, 1, 6, 4, 3, 5, 7, 8, 10, 9 };
Console.WriteLine("list");
var list1 = list.Select(i =>
{
Console.WriteLine("In select {0}", i);
return i * i;
}).ToList();
Console.WriteLine("list1");
var list2 = list1.Where(i =>
{
Console.WriteLine("In where>10 {0}", i);
return i > 10;
}).ToList();
Console.WriteLine("list2");
var list3 = list2.Where(i =>
{
Console.WriteLine("In where<60 {0}", i);
return i < 60;
}).ToList();
Console.WriteLine("list3");
var list4 = list3.OrderBy(i =>
{
Console.WriteLine("In orderby {0}", i);
return i;
}).ToList();
Console.WriteLine("list4");
var list5 = list4.ToList();
Console.WriteLine("list5");
foreach (var i in list5)
{
Console.WriteLine(i);
}

这样写打印出的结果为,

list
In select 2
In select 1
In select 6
In select 4
In select 3
In select 5
In select 7
In select 8
In select 10
In select 9
list1
In where>10 4
In where>10 1
In where>10 36
In where>10 16
In where>10 9
In where>10 25
In where>10 49
In where>10 64
In where>10 100
In where>10 81
list2
In where<60 36
In where<60 16
In where<60 25
In where<60 49
In where<60 64
In where<60 100
In where<60 81
list3
In orderby 36
In orderby 16
In orderby 25
In orderby 49
list4
list5
16
25
36
49

虽然也能得到正确的结果,但是却是不合理的。因为这样写每步都执行计算,并放到集合中,会有很大的性能损耗,失去了使用LINQ的优势。

何时进行真正计算是个值得思考的问题,多了会增加中间集合的数量,性能不好,少了有可能会有多次重复计算,性能也不好。下文会有说明。

如果使用Resharper插件,会提示出重复迭代(可能会有多次重复计算)的地方,这个功能很好,便于大家分析是否存在问题。

使用Max和Min要小心,Max和Min等聚合运算需要集合中存在值,否则会抛出异常,笔者多次遇到这个问题产生的BUG。

当前面有Where筛选时,后面使用Max或Min不一定是安全的,如下面的代码会抛出异常。

            var a = 0;
var list = new List<int> { 1, 2, 3 };
var min = list.Where(i => i < a).Min();
Console.WriteLine(min);

如果a来源于外部值,又有大段的逻辑,这样的BUG不易发现。

解决方法有多种,我们来分析一下,一种方法是可以先调一下Any,再使用Min,代码如下,

            var a = 0;
var list = new List<int> { 1, 2, 3 };
var list2 = list.Where(i => i < a);
var min = 0;
if (list2.Any())
{
min = list2.Min();
}
Console.WriteLine(min);

把代码改为如下,

            var a = 3;
var list = new List<int> { 1, 2, 3 };
var list2 = list.Where(i =>
{
Console.WriteLine("In where {0}", i);
return i < a;
});
var min = 0;
if (list2.Any(i =>
{
Console.WriteLine("In any {0}", i);
return true;
}))
{
min = list2.Min();
}
Console.WriteLine(min);

打印结果为,

In where 1
In any 1
In where 1
In where 2
In where 3
1

这样做有可能对性能影响不大,也有可能较大,取决于where(或前面的其他逻辑)中逻辑的多少和集合中前面不满足where条件的元素的数量。因为Any确定有就不会继续执行,但仍有部分重复计算发生。

第二种方法的代码如下,

            var a = 3;
var list = new List<int> { 1, 2, 3 };
var list2 = list.Where(i => i < a).ToList();
var min = 0;
if (list2.Any())
{
min = list2.Min();
}
Console.WriteLine(min);

这种方法不会有重复计算的开销,但会有数据导入集合的开销,和第一种比较哪种性能更高值得考虑。

第三种方法的代码如下,

            var a = 0;
var list = new List<int> { 1, 2, 3 };
var list2 = list.Where(i => i < a);
var min = 0;
try
{
min = list2.Min();
}
catch (Exception)
{
}
Console.WriteLine(min);

直接吃掉异常,数据量大时,前面过滤条件计算复杂时,可能这种方法性能最高。

总之,C#开发者,学好LINQ,用好LINQ,你会发现真的很爽的!

Linq研究的更多相关文章

  1. FCL研究-LINQ-System.Linq Enumerable

    .net 里面集合操作极为方便,尤其是实现了IEnumerable接口的集合,一直在使用,系统的研究一下集合的操作也是极好的. 类型 操作符名称 投影操作符 Select,SelectMany 限制操 ...

  2. .NET深入实战系列—Linq to Sql进阶

    最近在写代码的过程中用到了Linq查询,在查找资料的过程中发现网上的资料千奇百怪,于是自己整理了一些关于Linq中容易让人困惑的地方. 本文全部代码基于:UserInfo与Class两个表,其中Cla ...

  3. 开源Word读写组件DocX 的深入研究和问题总结

    一. 前言 前两天看到了asxinyu大神的[原创]开源Word读写组件DocX介绍与入门,正好我也有类似的自动生成word文档得需求,于是便仔细的研究了这个DocX. 我也把它融入到我的项目当中并进 ...

  4. Linq表达式和Lambda表达式用法对比

    什么是Linq表达式?什么是Lambda表达式?前一段时间用到这个只是,在网上也没找到比较简单明了的方法,今天就整理了一下相关知识,有空了再仔细研究研究 public Program() { List ...

  5. 本人为巨杉数据库(开源NoSQL)写的C#驱动,支持Linq,全部开源,已提交github

    一.关于NoSQL的项目需求 这些年在做AgileEAS.NET SOA 中间件平台的推广.技术咨询服务过程之中,特别是针对我们最熟悉的医疗行业应用之中,针对大数据分析,大并发性能的需求,我们也在慢慢 ...

  6. C# ORM中Dto Linq Expression 和 数据库Model Linq Expression之间的转换

    今天在百度知道中看到一个问题,研究了一会便回答了: http://zhidao.baidu.com/question/920461189016484459.html 如何使dto linq 表达式转换 ...

  7. Code First开发系列之管理数据库创建,填充种子数据以及LINQ操作详解

    返回<8天掌握EF的Code First开发>总目录 本篇目录 管理数据库创建 管理数据库连接 管理数据库初始化 填充种子数据 LINQ to Entities详解 什么是LINQ to ...

  8. Linq to Xml读取复杂xml(带命名空间)

    前言:xml的操作方式有多种,但要论使用频繁程度,博主用得最多的还是Linq to xml的方式,觉得它使用起来很方便,就用那么几个方法就能完成简单xml的读写.之前做的一个项目有一个很变态的需求:C ...

  9. C# LINQ详解(转)

    C# LINQ详解(一)   原文标题:How does it work in C#?-Part 3 (C# LINQ in detail),作者:Mohammand A Rahman. 目录 LIN ...

随机推荐

  1. django之全文检索

    全文检索 全文检索不同于特定字段的模糊查询,使用全文检索的效率更高,并且能够对于中文进行分词处理 haystack:django的一个包,可以方便地对model里面的内容进行索引.搜索,设计为支持wh ...

  2. 关注下Swoole

    面向生产环境的 PHP 异步网络通信引擎 使 PHP 开发人员可以编写高性能的异步并发 TCP.UDP.Unix Socket.HTTP,WebSocket 服务.Swoole 可以广泛应用于互联网. ...

  3. Nodejs&Express

    http://www.expressjs.comhttp://github.com/lelandtseng/form-datahttp://github.com/lelandtseng/mongo-m ...

  4. leetcode566

    public class Solution { public int[,] MatrixReshape(int[,] nums, int r, int c) { ); ); if (row * col ...

  5. 利用redis限制单个时间内某个mac地址的访问次数

    一.思路 用户mac地址唯一,可以作为redis中的key,每次请求进来,利用ttl命令,判断redis中key的剩余时间,如果大于零,则利用incr进行+1操作,然后再与总的限制次数作对比. 二.代 ...

  6. PHP 缓存插件之 Zend Opcache ( 取代 APC )

    简介: Zend Opcache .APC 这都是 PHP 代码加速器,将 PHP 源代码的编译结果缓存起来,再次调用时对比时间标记,如果没有改变则使用缓存数据,免去再次解析代码的开销. APC 止步 ...

  7. Logging模块总结 2018/5/30

    日志的级别 Level 用处 数字级别 DEBUG 详细的信息,在调试过程中用于诊断错误 10 INFO 用于确认事件正在运行 20 WARNING 意外发生时予以提醒,或者预测一些未来可能发生的一些 ...

  8. python3企业微信群组报警

    公司提出一个需求需要做一个企业微信的一个消息推送,需要将消息发送到聊天群里详细信息如下. 如何创建应用请阅读我的上篇文章:https://www.cnblogs.com/wangyajunblog/p ...

  9. NoSuchBeanDefinitionException:No qualifying bean of type found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency.

    报错如下: NoSuchBeanDefinitionException:No qualifying bean of type   found for dependency: expected at l ...

  10. Nginx 源码完全注释(11)ngx_spinlock

    Nginx 是多进程模式的,一个 master 与多个 workers,一般工作在多核 CPU 上,所以自旋锁就是必须用到的.Nginx 中的自旋锁的定义,位于 ngx_spinlock.c 中,如下 ...