LINQ
语言集成查询,提供了一个抽象层,类似SQL语法,允许不同数据源的查询使用相同的语法。
linq以扩展方法方式实现,扩展了IEnumerable<out T>接口,使用时需要使用System.Linq命名空间,在System.Linq.Enumerable定义了where、select等方法。linq语法实际也是转换成了这些方法执行,有些操作只能用扩展方法而没有对应的linq语法。
绝大部分linq语句在定义的时候是不会执行的,只有当foreach语句执行的时候才会真正执行;只有聚合操作会立即执行。
常见操作符及说明
操作符 | 说明 |
---|---|
Where OfType<TResult> | 筛选操作符定义了返回元素的条件。 在Where查询操作符中,可以使用调词。例如lambda表达式定义的谓词。来返回布尔值。 OfType<TResult>根据类型筛选元素,只返回TResult类型的元紊 |
Select SelectMany | 投射操作符用于把对象转换为另一个类型的新对象。Select和SelectMany定义了根据选择器函数选择结果值的投射 |
OrderBy ThenBy OrderByDescending ThenByDescending Reverse | 排序操作符改变所返回的元素的顺序。 OrderBy按升序排序, OrderByDescending 按降序排序。如果第一次排序的结果很类似,就可以使用ThenBy和ThenBy Descending操作符进行第二次排序。 Reverse反转集合中元素的顺序 |
Join GroupJoin | 连接操作符用于合并不直接相关的集合。使用Join操作符,可以根据键选择器函数连接两个集合,这类似于SOL中的JOIN。GroupJoin操作符连接两个集合,组合其结果 |
GroupBy ToLookup | 组合操作符把数据放在组中。GroupBy 操作符组合有公共键的元素。ToLookup通过创建一个一对多字典,来组合元素 |
Any All Contains | 如果元素序列满足指定的条件,限定符操作符就返回布尔值。Any、All和Contains 都是限定符操作符。Any确定集合中是否有满足谓词函数的元素;All确定集合中的所有元素是否都满足谓词函数; Contains检查某个元素是否在集合中 |
Take SkipWhile | 分区操作符返回集合的一个子集。Take、Skip、TakeWhile和 SkipWhile都是分区操作符。使用它们可以得到部分结果。使用Take必须指定要从集合中提取的元素个数:Skip跳过指定的元素个数,提取其他元素:TakeWhile提取条件为真的元素 |
Distinct Union Intersect Except Zip | Set操作符返回一个集合。 Distinct 从集合中删除重复的元素。除了Distinct之外,其他Set操作符都需要两个集合。 Union返回出现在其中一个集合中的唯一元素。 Intersect返回两个集合中都有的元素。 Except返回只出现在一个集合中的元素。 Zip把两个集合合并为一个 |
First FirstOrDefault Last LastOrDefault ElementAt ElementAtOrDefault Single SingleOrDefault | 这些元素操作符仅返回一个元素。 First返回第一个满足条件的元素。 FirstOrDefault类似于First,但如果没有找到满足条件的元素,就返回类型的默认值。Last:返回最后一个满足条件的元素。 ElementAt指定了要返回的元素的位置。 Single只返回一个满足条件的元素,如果有多个元素都满足条件,就抛出一个异常 |
Count Average | 聚合操作符计算集合的一个值!利用这些聚合操作符,可以计算所有值的总和、所有元素的个数、值最大和最小的元素i以及平均值等 |
ToArray | 这些转换操作符将集合转换为数组:IEnunerable、IList、IDictionary 等 |
Empty | 这些生成操作符返回一个新集合。 |
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace DemoTests.test
{
class Student
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public int ClassId { get; set; }
public decimal Score { get; set; }
public override string ToString()
{
return $"[{Id},{Code},{Name},{ClassId},{Score}]";
}
}
class Class
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public override string ToString()
{
return $"[{Id},{Code},{Name}]";
}
}
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
List<Student> stus = new List<Student>
{
new Student { Id = 1, ClassId = 1, Code = "01", Name = "张三", Score = 90 },
new Student { Id = 2, ClassId = 2, Code = "02", Name = "李四", Score = 67 },
new Student { Id = 3, ClassId = 2, Code = "03", Name = "王五", Score = 89 },
new Student { Id = 4, ClassId = 3, Code = "04", Name = "马六", Score = 73 },
new Student { Id = 5, ClassId = 3, Code = "05", Name = "小七", Score = 100 },
new Student { Id = 5, ClassId = 4, Code = "06", Name = "牙九", Score = 66 }
};
List<Class> classes = new List<Class>
{
new Class { Id = 1, Code = "001", Name = "一班" },
new Class { Id = 2, Code = "002", Name = "二班" },
new Class { Id = 3, Code = "003", Name = "三班" }
};
//简单查询
var state1 = from stu in stus
where stu.Score>80
orderby stu.Score
descending
select stu.Name;
foreach(var t in state1)
{
Console.WriteLine(t);
}
//关联查询,左连接
var state2 = from stu in stus
join c in classes on stu.ClassId equals c.Id into tt
from c in tt.DefaultIfEmpty()
select new { Stu = stu, Cls = c };
foreach(var t in state2)
{
Console.WriteLine(t);
}
//分组查询
var state3 = from stu in stus
group stu by stu.ClassId into t
select new { ClassId = t.Key, Count = t.Count() };
foreach(var t in state3)
{
Console.WriteLine(t);
}
}
}
}
并行LINQ
System.Linq.ParallelEnumerable,并行查询返回一个ParallelQuery<TSource>。
[TestMethod]
public void TestMethod2()
{
Random r = new Random();
var data = Enumerable.Range(0, 10000000).Select(x => r.Next(140)).ToList();
double aver = data.AsParallel().Where(x => Math.Log(x) < 4).Select(x => x).Average();
Console.WriteLine(aver);
}
表达式树
在LNQ to Objects 中,扩展方法需要将一个委托类型作为参数,这样就可以将lambda表达式赋予参数。lambda表达式也可以赋予Expression<T>类型的参数。C#编译器根据类型给lambda表达式定义不同的行为。如果类型是Expression<T>,编译器就从lambda表达式中创建一个表达式树,并存储在程序集中。这样,就可以在运行期间分析表达式树,并进行优化,以便于查询数据源。
除了使用委托之外,编译器还会把表达式树放在程序集中。表达式树可以在运行期间读取。表达式树从派生自抽象基类Expression的类中构建。Expression类与 Expression<T>不同。继承自Expression类的表达式类有 BinaryExpression、ConstantExpression、InvocationExpression,lambdaExpression、NewExpression、NewArrayExpression、TermaryExpression和 Unary Expression等。编译器会从lambda表达式中创建表达式树。
DisplayTree()方法在控制台上图形化地显示表达式树。其中传递了一个Expression对象,并根据表达式的类型,把表达式的一些信息写到控制台上。根据表达式的类型,递归地调用DisplayTree()方法。
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod3()
{
Expression<Func<int, bool>> expr = x => x.CompareTo(10) == 0;
DisplayExprTree(0, "lambda", expr);
}
public void DisplayExprTree(int indent,string msg,Expression expr)
{
string output = string.Format("{0} {1}. NodeType:{2}; Expr:{3}",
"".PadLeft(indent, ' '), msg, expr.NodeType, expr);
indent++;
switch(expr.NodeType)
{
case ExpressionType.Lambda:
Console.WriteLine(output);
LambdaExpression lambda = expr as LambdaExpression;
foreach(var p in lambda.Parameters)
{
DisplayExprTree(indent, "Parameter", p);
}
DisplayExprTree(indent, "Body", lambda.Body);
break;
case ExpressionType.Constant:
ConstantExpression constant = expr as ConstantExpression;
Console.WriteLine("{0} Const Value: {1}", output, constant.Value);
break;
case ExpressionType.Parameter:
ParameterExpression parameter = expr as ParameterExpression;
Console.WriteLine("{0} Param Type: {1}", output, parameter.Type.Name);
break;
case ExpressionType.Equal:
case ExpressionType.AndAlso:
case ExpressionType.GreaterThan:
BinaryExpression binary = expr as BinaryExpression;
if (binary.Method != null)
Console.WriteLine("{0} Method: {1}", output, binary.Method.Name);
else
Console.WriteLine(output);
DisplayExprTree(indent, "Left", binary.Left);
DisplayExprTree(indent, "Right", binary.Right);
break;
case ExpressionType.MemberAccess:
MemberExpression member = expr as MemberExpression;
Console.WriteLine("{0} Member Name: {1}, Type: {2}", output, member.Member.Name, member.Type.Name); ;
DisplayExprTree(indent, "Member Expr", member.Expression);
break;
default:
Console.WriteLine();
Console.WriteLine("{0} {1}", expr.NodeType, expr.Type.Name);
break;
}
}
}