知识须要不断积累、总结和沉淀,思考和写做是成长的催化剂javascript
1、Lambda表达式一、匿名方法二、Lambda表达式2、Linq概述3、查询操做符一、linq初见二、经常使用查询操做符筛选排序分组链接合并分页聚合转换4、并行Linq5、表达式树一、认识表达式目录树二、拼装表达式树三、应用6、小结php
使用delegate的时候不少时候不必使用一个普通方法,由于这个方法只有delegate会用,而且只用一次
,这时候使用匿名方法最合适。
匿名方法就是没有名字的方法
。示例中就是用一个匿名方法建立委托实例,咱们无需在写一个具名方法给委托,使代码更简洁和可读。匿名方法也是在调用时执行,在myDele(1,"test")处调用。(用反编译器看一下仍是会生成一个具名方法的,只不过在编译器内部使用)。java
delegate bool MyDelegate(int i, string s);
MyDelegate myDele = delegate (int i, string s)
{
Console.WriteLine($"我是匿名方法,参数值{i},{s}");
return true;
};
bool b = myDele(1, "test");
函数式编程,在C#3.0开始,咱们有了Lambda表达式代替匿名方法,它比匿名方法更加简单。Lambda运算符“=>”(发音goesto)
的左边列出了须要的参数,右边是利用该参数方法的实现代码。nginx
Action<string> a1 = delegate (string s) { Console.WriteLine(s); };
a1("匿名方法");
Action<string> a2 = (string s)=> { Console.WriteLine(s); };
a1("Lambda表达式");
Action<string> a3 = s => { Console.WriteLine(s); };
a3("Lambda表达式,有一个参数的能够简写不要小括号,参数类型会自动推断");
Action<string> a4 = s => Console.WriteLine(s);
a4("Lambda表达式,方法体只有一行,连花括号也能够省略");
另外一点,经过Lambda表达式能够访问Lambda表达式块外部的变量。这是一个很是好的功能,但若是未正确使用,也会很是危险。web
int sommVal = 5;
Func<int, int> f = x => x + sommVal;
sommVal = 7;
Console.WriteLine(f(3));
若是外部修改了sommVol值就会影响Lambda表达式的输出,特别是在多线程中,可能没法肯定当前的sommVal值。
Lambad表达式内部是如何使用外部的变量呢?首先编译器会建立一个匿名类,而后将使用到的外部变量当作匿名类的构造函数的参数,当调用时候,就建立匿名类的一个实例,并传递调用该方法时外部变量的值。sql
Linq(language integrated query)语言集成查询集成了C#编程语言中的查询语法,使之可使用相同的语法访问不一样的数据源
。
根据数据源的不一样,Linq可分为linq to object,linq to sql,linq to xml,你也能够扩展linq to excel,to everything。为不一样的数据源提供相同的查询接口便可。数据库
如今咱们有以下实体的集合express
public class Student
{
public int Id { get; set; }
public int ClassId { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
List<Student> studentLst = new List<Student>();
假设studentLst里已经有一些数据,而后须要查询出年纪小于25的学生。有不少方法,能够循环列表挑出age<25的学生,可使用List的FindAll,Where等方法,看起来像下面这样(注意只有在访问list中数据时,才会去执行过滤条件查询,延迟查询)编程
var list = studentLst.Where<Student>(s => s.Age < 25);
foreach (var item in list)
{
Console.WriteLine("Name={0} Age={1}", item.Name, item.Age);
}
where扩展方法的内部逻辑大概像这样,foreach循环调用过滤的委托方法,yield
关键字语法糖包装了一些复杂行为,包括会初始化一个IEnumerable类,而后给添加内容。缓存
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> func)
{
if (source == null)
{
throw new Exception("source is null");
}
if (func == null)
{
throw new Exception("func is null");
}
foreach (var item in source)
{
if (func.Invoke(item))
{
yield return item;
}
}
}
那么用Linq如何查询呢?
var list = from s in studentList
where s.Age < 25
select s;
foreach (var item in list)
{
Console.WriteLine("Name={0} Age={1}", item.Name, item.Age);
}
From、where、select
都是预约义的关键字,查询表达式必须以from开头,以select或group子句结束,中间可使用where、orderby、join等
。from子句引入数据源studentList和范围变量s,s就像foreach循环中的迭代变量。
一样的,在运行期间定义查询表达式时,查询不会当即运行,在迭代数据项时运行。
最多见的查询操做就是以布尔表达式的形式应用筛选器。经过where
子句筛选表达式为true的结果
var list = from s in studentList
where s.Age < 25 && s.ClassId==1
select s;
Orderby
子句根据要排序类型的默认比较器,对返回序列中的元素进行排序
var list = from s in studentList
where s.Age < 25
orderby s.Name ascending
select s;
和list的如下方法相似,就是把关键字解析为方法,随着查询愈来愈复杂,linq这种相似sql语句就表现的更加简洁直观
var list = studentList.Where(s => s.Age < 25).OrderByDescending(s => s.Name).Select(s => s);
group
子句用于对根据您指定的键所得到的结果进行分组。示例中into关键字建立进一步查询的标识,用select子句建立了一个带key和maxAge属性的匿名类型,返回每一个班级中年龄小于25岁的最大年龄。
var list = from s in studentList
where s.Age < 25
group s by s.ClassId into sg
select new
{
key = sg.Key,
maxAge = sg.Max(t => t.Age)
};
foreach (var item in list)
{
Console.WriteLine($"key={item.key} maxAge={item.maxAge}");
}
使用join
子句能够根据特性的条件合并两个数据源。例如经过链接查询选择相同课程的学生
List<Class> classList = new List<Class>(){
new Class()
{
Id=1,
ClassName="高数"
},
new Class()
{
Id=2,
ClassName="毛概"
}
};
var list = from s in studentList
join c in classList on s.ClassId equals c.Id
select new
{
Name = s.Name,
CalssName = c.ClassName
};
foreach (var item in list)
{
Console.WriteLine($"Name={item.Name},CalssName={item.CalssName}");
}
zip
方法是.NET4新增的,容许用一个函数把两个序列合并为一个。第一个集合中的第一项会与第二个集合中的第一项合并,第一个集合中的第二项与第二个集合中的第二项合并,以此类推。若是两个集合的项目不一样,zip方法就在到达较小集合的末尾时中止
。
var list = from s in studentList
where s.Age < 25
select s;
var list2 = from s in studentList
where s.Age < 25
select s;
var lst = list.Zip(list2, (first, second) => first.Name + "," + second.Name);
扩展方法Take()和Skip()
等的分区操做用于分页。使用时把扩展方法take、skip添加到查询的最后,skip方法会忽略根据页面大小和实际页数计算出的项数,再使用take方法根据页面大小提取必定数量的项
int pageSize = 5;
int pageIdx = 0;
var list = (from s in studentList
where s.Age < 25
select s).Skip(pageIdx * pageSize).Take(pageSize);
聚合操做符Count(),Sum(),Min(),Average()
等不返回一个序列,而返回一个值。
Linq不仅是检索数据。 它也是用于转换数据的强大工具。 经过使用 LINQ 查询,可使用源序列做为输入,并经过多种方式对其进行修改,以建立新的输出序列。 经过排序和分组,你能够修改序列自己,而无需修改这些元素自己。 但也许 LINQ 查询最强大的功能是建立新类型
。
如下示例将内存中数据结构中的对象转换为 XML 元素。
var studentsToXML = new XElement("Root",
from student in studentList
select new
XElement("student",
new XElement("name", student.Name),
new XElement("age", student.Age)
)
);
Console.WriteLine(studentsToXML);
.NET4在System.Linq名称空间中包含了一个新类ParallelEnumerable
,能够分解查询的工做使其分布在多个线程上。集合序列会分红多个部分,不一样的线程处理,完成后合并。这对大集合,又是多核CPU的能够提升效率
var list = (from s in studentList.AsParallel()
where s.Age < 25
select s.Age).Sum();
var list2 = (from s in Partitioner.Create(studentList,true).AsParallel().WithDegreeOfParallelism(8)
where s.Age < 25
select s.Age).Sum();
可使用Partitioner类建立分区器,WithDegreeOfParallelism指定最大并行任务数
。
并行linq每每须要较多耗时使用,那应该也有取消长时间运行的任务需求。给查询添加一个WithCancellation方法
,并传递一个CancellationToken令牌
做为参数。该查询在单独线程中使用,主线程中触发取消命令。
var cts = new CancellationTokenSource();
new Thread(()=>
{
try
{
var sum= (from s in studentList.AsParallel().WithCancellation(cts.Token)
where s.Age < 25
select s.Age).Sum();
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex.Message);
}
}
).Start();
//外部动做触发取消
cts.Cancel();
出如今System.Linq.Expression
中,就是为Linq to sql服务的。表达式树以树形数据结构表示代码,其中每个节点都是一种表达式。它能够将咱们原来直接由代码编写的逻辑存储在一个树状的结构
里,而后运行的时候就去动态解析这个树。lambda表达式声明表达式目录树能够像下面这样。
Func<int, int, int> func = (m, n) => m * n + 2;// new Func<int, int, int>((m, n) => m * n + 2);
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;//lambda表达式声明表达式目录树
//Expression<Func<int, int, int>> exp1 = (m, n) =>//只能一行 不能有大括号
// {
// return m * n + 2;
// };
//Queryable //a=>a.Id>3
//表达式目录树:语法树,或者说是一种数据结构;能够被咱们解析
int iResult1 = func.Invoke(12, 23);
int iResult2 = exp.Compile().Invoke(12, 23);
若是使用Expression
类接口声明看起来会像下面这样,注意比较Lambda表达式声明和Expression类本身拼装声明的区别,最后都是须要Compile()
编译后执行。
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n");
var multiply = Expression.Multiply(parameterExpression, parameterExpression2);
var constant = Expression.Constant(2, typeof(int));
var add = Expression.Add(multiply, constant);
Expression<Func<int, int, int>> expression =
Expression.Lambda<Func<int, int, int>>(
add,
new ParameterExpression[]
{
parameterExpression,
parameterExpression2
});
int iResult1 = exp.Compile().Invoke(11, 12);
int iResult2 = expression.Compile().Invoke(11, 12);
表达式树是由表达式的主体body、表达式的参数parameters、表达式类型Type、返回类型NodeType组成
。一个树可能有不少叶子节点,复杂一点的例子像下面这样
//i*j+w*x
ParameterExpression a = Expression.Parameter(typeof(int), "i"); //建立一个表达式树中的参数,做为一个节点,这里是最下层的节点
ParameterExpression b = Expression.Parameter(typeof(int), "j");
BinaryExpression r1 = Expression.Multiply(a, b); //这里i*j,生成表达式树中的一个节点,比上面节点高一级
ParameterExpression c = Expression.Parameter(typeof(int), "w");
ParameterExpression d = Expression.Parameter(typeof(int), "x");
BinaryExpression r2 = Expression.Multiply(c, d);
BinaryExpression result = Expression.Add(r1, r2); //运算两个中级节点,产生终结点
Expression<Func<int, int, int, int, int>> lambda = Expression.Lambda<Func<int, int, int, int, int>>(result, a, b, c, d);
Console.WriteLine(lambda + ""); //输出‘(i,j,w,x)=>((i*j)+(w*x))’,z对应参数b,p对应参数a
Func<int, int, int, int, int> f = lambda.Compile(); //将表达式树描述的lambda表达式,编译为可执行代码,并生成该lambda表达式的委托;
Console.WriteLine(f(1, 1, 1, 1) + ""); //输出结果2
上面例子造成的表达式树就像下面这样。
最经常使用的地方仍是查询数据
时。以往咱们作一个查询,根据用户输入,去数据库中查询匹配的信息,可能去想到去拼一条带where条件的sql语句,而后去执行这条sql便可。
若是没法肯定须要查询的字段,当每换一个查询条件或组合多个查询条件,咱们能够用表达式目录树动态的拼装起来。
还能够用来代替反射
,咱们知道反射有性能问题,硬编码是最快的,但不够灵活。像泛型同样,表达式树能够动态生成硬编码,缓存后之后访问调用就至关于硬编码性能。好比示例中,咱们若是须要对一个类型对象转换成另外一个对象。这里能够有不少方法,硬编码、反射、序列化等均可以实现,如今咱们用表达式树试一下。
People people = new People()
{
Id = 11,
Name = "Wang",
Age = 31
};
PeopleCopy peopleCopy = new PeopleCopy()
{
Id = people.Id,
Name = people.Name,
Age = people.Age
};
硬编码像上面这样,咱们用表达式目录树就是为了可以生成这种硬编码的委托
。
public class ExpressionMapper
{
private static Dictionary<string, object> _Dic = new Dictionary<string, object>();
/// <summary>
/// 字典缓存表达式树
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
/// <param name="tIn"></param>
/// <returns></returns>
public static TOut Trans<TIn, TOut>(TIn tIn)
{
string key = string.Format("funckey_{0}_{1}", typeof(TIn).FullName, typeof(TOut).FullName);
if (!_Dic.ContainsKey(key))
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
List<MemberBinding> memberBindingList = new List<MemberBinding>();
foreach (var item in typeof(TOut).GetProperties())
{
MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
foreach (var item in typeof(TOut).GetFields())
{
MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
{
parameterExpression
});
Func<TIn, TOut> func = lambda.Compile();//拼装是一次性的
_Dic[key] = func;
}
return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn);
}
}
var result = ExpressionMapper.Trans<People, PeopleCopy>(people);
表达式和表达式树什么关系呢?首先表达式是匿名方法生成委托实例,而表达式树是一种数据结构,自己不能执行的,须要编译成sql,而后解释执行表达式树中每一个节点的表达式。
本章认识了Lambda表达式,linq查询以及相关的经常使用操做符,它们不只用于筛选数据源,给数据源排序,还用于执行分区,分组,转换,链接等操做,使用并行linq能够提升大型数据集的查询效率。另外一个重要概念就是表达式目录树。表达式目录树容许在运行期间构建对数据源的查询,存储在程序集中,主要用在linq to sql中,后面学习EntityFramework框架时会用到大量的表达式目录树。
debug everything
愿一觉醒来,阳光正好
而不是,一觉醒来,天都黑了
若是手机在手边,也能够关注下vx:xishaobb,互动或获取更多消息。固然这里也一直更新de,下期见,拜了个拜拜。