结构伪类选择器
  • 第1个
  • 第2个
  • 第3个
  • 第4个
  • 第5个
  • 第6个
  • 第7个
伪类选择器其中的 链接伪类选择器 主要针对于 a

权限系统

RBAC基本概念

基于角色的权限访问控制(Role-Based Access Control)作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注。

RBAC 支持三个著名的安全原则:最小权限原则、责任分离原则和数据抽象原则

  • 最小权限原则之所以被 RBAC 所支持,是因为 RBAC 可以将其角色配置成其完成任务所需要的最小的权限集。
  • 责任分离原则可以通过调用相互独立互斥的角色来共同完成敏感的任务而体现,比如要求同一个计账员和财务管理员共同参与同一过账。
  • 数据抽象可以通过权限的抽象来体现,如账务操作用借款、存款等抽象权限,而不用操作系统提供的典型读、写、执行权限。然而这些原则必须通过 RBAC 各部件的详细配置才能得以体现。

CSS-Web高级程序设计

1. 有关 XHTML 和 CSS 的最佳实例

1.1. 把结构和表现标记硬挤到一起

1.2. 学习并热衷于使用标记

1.2.1. XHTML:新热点

HTML 有块级元素(如div、p、table等)和内联元素(如a、em、strong等)之分,内联元素永远不能包含块级元素。

XHTML 特点

  • DOCTYPE 声明
  • 保持标记具有良好的架构
  • 关闭每一个元素
  • 把元素和属性值设置为小写
  • 必须为每个属性指定一个值

1.2.2. 从结构提取样式

1.3. CSS:添加样式层

1.3.1. 更好的了解选择符

简单的样式规则范例

  • 1.类型选择符
1
2
3
h1 {
color:#36C;
}
  • 2.通配选择符
1
2
3
* {
color:#000;
}
  • 3.后代选择符
1
2
3
ul em {
text-transform:uppercase;
}
  • 4.类选择符
1
2
3
.text {
border:1px solid :#C00;
}
  • 5.id选择符
1
2
3
h1 #page-title {
text-align:right;
}

1.3.2. 其他选择符

  • 1.子选择符
1
2
3
body>p {
font-weight:bold;
}

大于符号 > 指示用户代理选择子一级的所有 p 元素而不是所有后代。

  • 2.属性选择符

属性选择符

属性选择符

1.3.3. 多重声明组合

1.3.4. 对选择符进行分组

1.3.5. 继承

属性选择符

  • 1.检查元素的层次

不是氖属性都可以继承,外边距和内边距就是两个例外。这些属性只能单独应用于某个元素,而不能被其后代继承。

  • 2.重写继承

1.3.6. 综合应用

1.4. 了解层叠

1.4.1. 探寻样式来源

样式表三个方面来源

  • 用户代理
  • 用户
  • 作者

样式重要排序

1.4.2. 根据优先级排序

把理论应用于实践

基于可靠浏览器进行构建

CSS hack:基于不同浏览器写不同CSS代码的过程

理性对待 hack

box模型

Google 的 blogger.com 翻转器和设计思想

设计人员访谈

CSS 驱动的翻转器

改变链接的颜色和背景色(简单)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style>
a {
border-bottom: 1px solid #eee;
color: #d17e62;
text-decoration: none;
}

a:visited {
border-bottom: 1px solid #eee;
color: #09d604c;
text-decoration: none;
}

a:hover {
background-color: #ffffda;
border-bottom: 1px solid #ddd;
color: #c30;
text-decoration: none;
}
</style>

<p>If you're interested then<a href="">bung me an email</a> and we can talk about what you want</p>

改变链接的颜色和背景色(复杂)

Web前端开发环境搭建

插件安装

  • Nodejs
  • grunt
  • bower
  • express
  • supervisor

grunt

1
npm install -g grunt cli

Web前端开发环境搭建

bower

1
npm install bower -g

Web前端开发环境搭建

验证是否安装成功

Web前端开发环境搭建

express

安装express,在cmd中输入:npm express -gd; -g代表安装到NODE_PATH的lib里面,而-d代表把相依性套件也一起安装。如果沒有-g的话会安装目前所在的目录。

JQuery源码解读

AMD规范

什么是ADM

全称是Asynchronous Module Definition,即异步模块加载机制。
从它的规范描述页面看,AMD很短也很简单,但它却完整描述了模块的定义,依赖关系,引用关系以及加载机制。从它被requireJS,NodeJs,Dojo,JQuery使用也可以看出它具有很大的价值,没错,JQuery近期也采用了AMD规范。在这篇文章中,我们就将介绍AMD的性质,用法,优势以及应用场景。从AMD中我们也能学习到如何在更高层面去设计自己的前端应用。

AMD构成

作为一个规范,只需定义其语法API,而不关心其实现。AMD规范简单到只有一个API,即define函数:

1
define([module-name?],[array-of-dependencies?],[module-factory-or-object])

其中:

  • module-name:模块标识,可以省略。
  • array-of-dependencies:所依赖的模块,可以省略。
  • module-factory-or-object:模块的实现,或者一个JavaScript对象。

从这个define函数AMD中的A:Asynchronous,我们也不难想到define函数具有的另外一个性质,异步性。当define函数执行时,它首先会异步的去调用第二个参数中列出的依赖模块,当所有的模块被载入完成之后,如果第三个参数是一个回调函数则执行,然后告诉系统模块可用,也就通知了依赖于自己的模块自己已经可用。

匿名模块

define 方法允许你省略第一个参数,这样就定义了一个匿名模块,这时候模块文件的文件名就是模块标识。

jQuery 对象与 dom 对象转换

jQuery 转换成 dom 对象

  • [index]
1
2
var $j =$("#j") ; //jQuery对象
var d=$j[0]; //DOM对象
  • .get(index)
1
2
var $j=$("#v"); //jQuery对象
var d=$j.get(0); //DOM对象

dom 对象转换成 jQuery

对于已经是一个DOM对象,只需要用$()把DOM对象包装起来,就可以获得一个jQuery对象了。

1
2
var v=document.getElementById("v"); //DOM对象
var $v=$(v); //jQuery对象

$.data()

$.data(dom对象, ‘tree’);

$.extend()的深拷贝和浅拷贝详细讲解

1
语法:jQuery.extend([deep],target,object1[,objectN])
  • 浅拷贝(false默认):如果第二个参数对象有的属性第一个参数对象也有,那么不会进行相同参数内部比较,直接将第一个对象的相同参数覆盖。
  • 深拷贝:如果第二个参数对象有的属性第一个参数对象也有,还要继续在这个相同的参数向下一层找,比较相同参数的对象中是否还有一样的属性,如果有,将其继承到第一个对象,如果没有,则覆盖。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var object1 = {
apple: 0,
banana: {
weight: 52,
price: 100
},
cherry: 97
};
var object2 = {
banana: {
price: 200
},
durian: 100
};

//默认情况浅拷贝
//object1--->{"apple":0,"banana":{"price":200},"cherry":97,"durian":100}
//object2的banner覆盖了object1的banner,但是weight属性未被继承
//$.extend(object1, object2);

//深拷贝
//object1--->{"apple":0,"banana":{"weight":52,"price":200},"cherry":97,"durian":100}
//object2的banner覆盖了object1的banner,但是weight属性也被继承了呦
$.extend(true,object1, object2);

console.log('object1--->'+JSON.stringify(object1));

jQuery 的应用

$.each的用法

1
2
3
4
$.each(parentData,function(index,childData){
index ; //选择器的index位置
childData; //当前元素
})

checkbox

1、获取单个checkbox选中项(三种写法)

1
2
3
$("input:checkbox:checked").val()
$("input:[type='checkbox']:checked").val();
$("input:[name='ck']:checked").val();

2、 获取多个checkbox选中项

1
2
3
4
5
$('input:checkbox').each(function() {
if ($(this).attr('checked') ==true) {
alert($(this).val());
}
});

3、设置第一个checkbox 为选中值

1
2
$('input:checkbox:first').attr("checked",'checked');
$('input:checkbox').eq(0).attr("checked",'true');

4、设置最后一个checkbox为选中值

1
2
$('input:radio:last').attr('checked', 'checked');
$('input:radio:last').attr('checked', 'true');

5、根据索引值设置任意一个checkbox为选中值

1
2
$('input:checkbox).eq(索引值).attr('checked', 'true');索引值=0,1,2....
$('input:radio').slice(1,2).attr('checked', 'true');

6、选中多个checkbox同时选中第1个和第2个的checkbox

1
$('input:radio').slice(0,2).attr('checked','true');

7、根据Value值设置checkbox为选中值

1
$("input:checkbox[value='1']").attr('checked','true');

8、删除Value=1的checkbox

1
$("input:checkbox[value='1']").remove();

9、删除第几个checkbox

1
2
3
$("input:checkbox").eq(索引值).remove();索引值=0,1,2....
// 如删除第3个checkbox:
$("input:checkbox").eq(2).remove();

10、遍历checkbox

1
2
3
$('input:checkbox').each(function (index, domEle) {
//写入代码
});

11、全部选中

1
2
3
$('input:checkbox').each(function() {
$(this).attr('checked', true);
});

12、全部取消选择

1
2
3
$('input:checkbox').each(function () {
$(this).attr('checked',false);
});

Emmet语法

1. Emmet Html 语法

1.1. 元素

  • 输入元素名称,自动生成标签,如 div
1
<div></div>
  • 输入 ! 或 html:5 自动补全基本结构
1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
</body>
</html>

1.2. 嵌套操作

  • child:使用 “>” 生成子元素

div>ul>li

1
2
3
4
5
<div>
<ul>
<li></li>
</ul>
</div>
  • Sibling: 使用符号 “+” 生成兄弟元素

div+p+bq

1
2
3
<div></div>
<p></p>
<blockquote></blockquote>
  • Climb-up:使用 “^” 生成父元素,与 “>” 相反

div+div>p>span+em^bq

1
2
3
4
5
<div></div>
<div>
<p><span></span><em></em></p>
<blockquote></blockquote>
</div>
  • Multiplication:使用 “*” 操作符生成多个元素

div>ul>li*5

1
2
3
4
5
6
7
8
9
<div>
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
  • Grouping:使用 “()” 操作符将元素分组,实现更复杂的简写任务

div>(header>ul>li*2)+footer>p

1
2
3
4
5
6
7
8
9
10
11
<div>
<header>
<ul>
<li></li>
<li></li>
</ul>
</header>
<footer>
<p></p>
</footer>
</div>

1.3. 属性操作

  • id 与 class

简写时,元素与 id 属性值之间用 “#” 分隔,与 class 属性值之间用 “.” 分隔。

div#header+div.page+div#footer.class1.class2.class3

1
2
3
<div id="header"></div>
<div class="page"></div>
<div id="footer" class="class1 class2 class3"></div>
  • 其它属性

使用 [attr] 标记添加其他属性。

1
<td title="hello" colspan="3"></td>

注意:

- 方括号中可添加任意数量的属性
- 不给定属性值,则属性值为""。td[colspan title]将得到

1
<td colspan="" title=""></td>
- 属性值可用单引号或双引号,输出统一为双引号 - 如果属性值中没有空格,则引号可省略
  • 为条目编号
1
2
3
4
5
li.item$*3

<li class="item1"></li>
<li class="item2"></li>
<li class="item3"></li>

可在 “$” 后添加 “@n” 修改编号的起始值为n。

1
2
3
4
5
li.item$@3*3

<li class="item3"></li>
<li class="item4"></li>
<li class="item5"></li>

可在 “$” 后添加 “@-” 修改编号的方向。

1
2
3
4
5
li.item$@-3*3

<li class="item5"></li>
<li class="item4"></li>
<li class="item3"></li>

1.4. 添加文本

使用花括号 “{}” 操作符为元素添加文本节点。

1
2
3
4
5
// before
a[href=me.htm]{click me}

// after
<a href="me.htm">click me</a>

因为文本也是节点,所以 a[href=me.htm]{click me} 与 a[href=me.htm]>{click me} 等价。

但有多个元素时则要注意。

1
2
3
4
5
6
7
8
9
10
11
// before
a[href=me.htm]{click me}+p{ok}
a[href=me.htm]>{click me}+p{ok}

// after
<a href="me.htm">click me</a>
<p>ok</p>

<a href="me.htm">click me
<p>ok</p>
</a>

打造自己的linqProvider

认识表达式树

表达式树是一种抽象语法或者数据结构,通过解析表达式目录可以实现一些特定功能。

如何构造表达式树,最简单的方法莫过于使用 Lambda 表达式

1
Expression<Func<int,int,int> expression = (a,b) => a*b +2;

在我们将Lambda表达式指定给Expression<TDelegate>类型的变量(参数)时,编译器将会发出生成表达式目录树的指令,如上面这段代码中的Lambda表达式(a, b) => a * b + 2将创建一个表达式目录树,它表示的是一种数据结构,即我们把一行代码用数据结构的形式表示了出来,具体来说最终构造出来的表达式目录树形状如下图所示:

表达式目录树形状

这里每一个节点都表示一个表达式,可能是一个二元运算,也可能是一个常量或者参数等,如上图中的ParameterExpression就是一个参数表达式,ConstantExpression是一个常量表达式,BinaryExpression是一个二元表达式。我们也可以在Visual Studio中使用Expression Tree Visualizer来查看该表达式目录树:

.NET Framework到底提供的表达式类型:

表达式类型

它们都继承于抽象的基类Expression,而泛型的Expression则继承于LambdaExpression。在Expression类中提供了大量的工厂方法,这些方法负责创建以上各种表达式对象,如调用Add()方法将创建一个表示不进行溢出检查的算术加法运算的BinaryExpression对象,调用Lambda方法将创建一个表示lambda 表达式的LambdaExpression对象,具体提供的方法大家可以查阅MSDN。上面构造表达式目录树时我们使用了Lambda表达式,现在我们看一下如何通过这些表达式对象手工构造出一个表达式目录树,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void Main(string[] args)
{
ParameterExpression paraLeft = Expression.Parameter(typeof(int), "a");
ParameterExpression paraRight = Expression.Parameter(typeof(int), "b");

BinaryExpression binaryLeft = Expression.Multiply(paraLeft, paraRight);
ConstantExpression conRight = Expression.Constant(2, typeof(int));

BinaryExpression binaryBody = Expression.Add(binaryLeft, conRight);

LambdaExpression lambda =
Expression.Lambda<Func<int, int, int>>(binaryBody, paraLeft, paraRight);

Console.WriteLine(lambda.ToString());

Console.Read();
}

对于一个表达式目录树来说,它有几个比较重要的属性:

  • Body:指表达式的主体部分;
  • Parameters:指表达式的参数;
  • NodeType:指表达式的节点类型,如在上面的例子中,它的节点类型是Lambda;
  • Type:指表达式的静态类型,在上面的例子中,Type为Fun<int,int,int>。

表达式目录树与委托

大家可能经常看到如下这样的语言,其中第一句是直接用Lambda表达式来初始化了Func委托,而第二句则使用Lambda表达式来构造了一个表达式目录树,它们之间的区别是什么呢?

1
2
3
4
5
6
static void Main(string[] args)
{
Func<int, int, int> lambda = (a, b) => a + b * 2;

Expression<Func<int, int, int>> expression = (a, b) => a + b * 2;
}

其实看一下IL就很明显,其中第一句直接将Lambda表达式直接编译成了IL,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.method private hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
.maxstack 3
.locals init ([0] class [System.Core]System.Func`3<int32,int32,int32> lambda)
IL_0000: nop
IL_0001: ldsfld class [System.Core]System.Func`3<int32,int32,int32>
TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0006: brtrue.s IL_001b
IL_0008: ldnull
IL_0009: ldftn int32 TerryLee.LinqToLiveSearch.Program::'<Main>b__0'(int32,
int32)
IL_000f: newobj instance void class [System.Core]System.Func`3<int32,int32,int32>::.ctor(object,
native int)
IL_0014: stsfld class [System.Core]System.Func`3<int32,int32,int32>
TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0019: br.s IL_001b
IL_001b: ldsfld class [System.Core]System.Func`3<int32,int32,int32>
TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0020: stloc.0
IL_0021: ret
}

而第二句,由于告诉编译器是一个表达式目录树,所以编译器会分析该Lambda表达式,并生成表示该Lambda表达式的表达式目录树,即它与我们手工创建表达式目录树所生成的IL是一致的,如下代码所示,此处为了节省空间省略掉了部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
.method private hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
.maxstack 4
.locals init ([0] class [System.Core]System.Linq.Expressions.Expression`1<
class [System.Core]System.Func`3<int32,int32,int32>> expression,
[1] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0000,
[2] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0001,
[3] class [System.Core]System.Linq.Expressions.ParameterExpression[] CS$0$0002)
IL_0000: nop
IL_0001: ldtoken [mscorlib]System.Int32
IL_0006: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(...)
IL_000b: ldstr "a"
IL_0010: call class [System.Core]System.Linq.Expressions.ParameterExpression
[System.Core]System.Linq.Expressions.Expression::Parameter(
class [mscorlib]System.Type,

IL_0038: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle()
IL_003d: call class [System.Core]System.Linq.Expressions.ConstantExpression
[System.Core]System.Linq.Expressions.Expression::Constant(object,
class [mscorlib]System.Type)
IL_0042: call class [System.Core]System.Linq.Expressions.BinaryExpression
[System.Core]System.Linq.Expressions.Expression::Multiply(class [System.Core]System.Linq.Expressions.Expression,
class [System.Core]System.Linq.Expressions.Expression)
IL_0047: call class [System.Core]System.Linq.Expressions.BinaryExpression
[System.Core]System.Linq.Expressions.Expression::Add(class [System.Core]System.Linq.Expressions.Expression,
class [System.Core]System.Linq.Expressions.Expression)
IL_004c: ldc.i4.2
IL_004d: newarr [System.Core]System.Linq.Expressions.ParameterExpression
}

现在相信大家都看明白了,这里讲解它们的区别主要是为了加深大家对于表达式目录树的区别。

执行表达式目录树

前面已经可以构造出一个表达式目录树了,现在看看如何去执行表达式目录树。我们需要调用Compile方法来创建一个可执行委托,并且调用该委托,如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void Main(string[] args)
{
ParameterExpression paraLeft = Expression.Parameter(typeof(int), "a");
ParameterExpression paraRight = Expression.Parameter(typeof(int), "b");

BinaryExpression binaryLeft = Expression.Multiply(paraLeft, paraRight);
ConstantExpression conRight = Expression.Constant(2, typeof(int));

BinaryExpression binaryBody = Expression.Add(binaryLeft, conRight);

Expression<Func<int, int, int>> lambda =
Expression.Lambda<Func<int, int, int>>(binaryBody, paraLeft, paraRight);

Func<int, int, int> myLambda = lambda.Compile();

int result = myLambda(2, 3);
Console.WriteLine("result:" + result.ToString());

Console.Read();
}

运行后输出的结果:

lambda_result

这里我们只要简单的调用Compile方法就可以了,事实上在.NET Framework中是调用了一个名为ExpressionCompiler的内部类来做表达式目录树的执行(注意此处的Compiler不等同于编译器的编译)。另外,只能执行表示Lambda表达式的表达式目录树,即LambdaExpression或者Expression<TDelegate>类型。如果表达式目录树不是表示Lambda表达式,需要调用Lambda方法创建一个新的表达式。如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
static void Main(string[] args)
{
BinaryExpression body = Expression.Add(
Expression.Constant(2),
Expression.Constant(3));

Expression<Func<int>> expression =
Expression.Lambda<Func<int>>(body, null);

Func<int> lambda = expression.Compile();

Console.WriteLine(lambda());
}

访问与修改表达式目录树

在.NET Framework中,提供了一个抽象的表达式目录树访问类ExpressionVisitor,但它是一个internal的,我们不能直接访问。幸运的是,在MSDN中微软给出了ExpressionVisitor类的实现,我们可以直接拿来使用。该类是一个抽象类,微软旨在让我们在集成ExpressionVisitor的基础上,实现自己的表达式目录树访问类。现在我们来看简单的表达式目录树:

1
2
3
4
5
6
static void Main(string[] args)
{
Expression<Func<int, int, int>> lambda = (a, b) => a + b * 2;

Console.WriteLine(lambda.ToString());
}

输出后为:

result2

现在我们想要修改表达式目录树,让它表示的Lambda表达式为(a,b)=>(a - (b * 2)),这时就需要编写自己的表达式目录树访问器,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class OperationsVisitor : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return Visit(expression);
}

protected override Expression VisitBinary(BinaryExpression b)
{
if (b.NodeType == ExpressionType.Add)
{
Expression left = this.Visit(b.Left);
Expression right = this.Visit(b.Right);
return Expression.Subtract(left,right);
}

return base.VisitBinary(b);
}
}

使用表达式目录树访问器来修改表达式目录树,如下代码所示:

1
2
3
4
5
6
7
8
9
static void Main(string[] args)
{
Expression<Func<int, int, int>> lambda = (a, b) => a + b * 2;

var operationsVisitor = new OperationsVisitor();
Expression modifyExpression = operationsVisitor.Modify(lambda);

Console.WriteLine(modifyExpression.ToString());
}

结果如下:

result3

似乎我们是修改表达式目录树,其实也不全对,我们只是修改表达式目录树的一个副本而已,因为表达式目录树是不可变的,我们不能直接修改表达式目录树,看看上面的OperationsVisitor类的实现大家就知道了,在修改过程中复制了表达式目录树的节点。

为什么需要表达式目录树

就拿LINQ to SQL为例

linq_to_sql

当我们在C#语言中编写一个查询表达式时,它将返回一个IQueryable类型的值,在该类型中包含了两个很重要的属性Expression和Provider,如下面的代码:

IQueryAble

我们编写的查询表达式,将封装为一种抽象的数据结构,这个数据结构就是表达式目录树,当我们在使用上面返回的值时,编译器将会以该值所期望的方式进行翻译,这种方式就是由Expression和Provider来决定。可以看到,这样将会非常的灵活且具有良好的可扩展性,有了表达式目录树,可以自由的编写自己的Provider,去查询我们希望的数据源。经常说LINQ为访问各种不同的数据源提供了一种统一的编程方式,其奥秘就在这里。然而需要注意的是LINQ to Objects并不需要任何特定的LINQ Provider,因为它并不翻译为表达式目录树,后面会说到这一点。

IEnumerable<T>接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main(string[] args)
{
List<String> myList = new List<String>() { "a", "ab", "cd", "bd" };

IEnumerable<String> query = from s in myList
where s.StartsWith("a")
select s;

foreach (String s in query)
{
Console.WriteLine(s);
}

Console.Read();
}

result4

为什么在LINQ to Objects中返回的是IEnumerable类型的数据而不是IQueryable呢?
在LINQ to Objects中查询表达式或者Lambda表达式并不翻译为表达式目录树,因为LINQ to Objects查询的都是实现了IEnmerable接口的数据,所以查询表达式或者Lambda表达式都可以直接转换为.NET代码来执行,无需再经过转换为表达式目录这一步,这也是LINQ to Objects比较特殊的地方,它不需要特定的LINQ Provider。我们可以看一下IEnumerable接口的实现,它里面并没有Expression和Provider这样的属性,如下图所示:

1
2
3
4
public interface IEnumerable<T>:Ienumerable
{
IEnumerator<T> GetEnumerator();
}

至于LINQ to Objects中所有的标准查询操作符都是通过扩展方法来实现的,它们在抽象类Enumerable中定义,如其中的Where扩展方法如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static class Enumerable
{
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
if (predicate == null)
{
throw Error.ArgumentNull("predicate");
}
return WhereIterator<TSource>(source, predicate);
}

public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, int, bool> predicate)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
if (predicate == null)
{
throw Error.ArgumentNull("predicate");
}
return WhereIterator<TSource>(source, predicate);
}
}

注意到这里方法的参数Func系列委托,而非Expression<Func>,在本文的后面,你将看到,IQueryable接口的数据,这些扩展方法的参数都是Expression<Func>.

同样还有一点需要说明的是,在IEnumerable中提供了一组扩展方法AsQueryable(),可以用来把一个IEnumerable类型的数据转换为IQueryable类型,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void Main(string[] args)
{
var myList = new List<String>()
{ "a", "ab", "cd", "bd" }.AsQueryable<String>();

IQueryable<String> query = from s in myList
where s.StartsWith("a")
select s;

foreach (String s in query)
{
Console.WriteLine(s);
}

Console.Read();
}

运行这段代码,虽然它的输出结果与上面的示例完全相同,但它们查询的机制却完全不同:

IQueryable<T>接口

IQueryable定义

这里有两个很重要的属性Expression和Provider,分别表示获取与IQueryable 的实例关联的表达式目录树和获取与此数据源关联的查询提供程序,我们所有定义在查询表达式中方法调用或者Lambda表达式都将由该Expression属性表示,而最终会由Provider表示的提供程序翻译为它所对应的数据源的查询语言,这个数据源可能是数据库,XML文件或者是WebService等。该接口非常重要,在我们自定义LINQ Provider中必须要实现这个接口。同样对于IQueryable的标准查询操作都是由Queryable中的扩展方法来实现的,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static class Queryable
{
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
if (predicate == null)
{
throw Error.ArgumentNull("predicate");
}
return source.Provider.CreateQuery<TSource>(
Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod())
.MakeGenericMethod(new Type[] { typeof(TSource) }),
new Expression[] { source.Expression, Expression.Quote(predicate) }));
}

public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source,
Expression<Func<TSource, int, bool>> predicate)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
if (predicate == null)
{
throw Error.ArgumentNull("predicate");
}
return source.Provider.CreateQuery<TSource>(
Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod())
.MakeGenericMethod(new Type[] { typeof(TSource) }),
new Expression[] { source.Expression, Expression.Quote(predicate) }));
}
}

最后还有一点,如果我们定义的查询需要支持Orderby等操作,还必须实现IOrderedQueryable<T> 接口,它继承自IQueryable<T>,如下图所示:

IOrderedQueryable

IQueryProvider接口

在认识了IQueryable接口之后,我们再来看看在自定义LINQ Provider中另一个非常重要的接口IQueryProvider。它的定义如下图所示:

IQueryProvider

看到这里两组方法的参数,其实大家已经可以知道,Provider负责执行表达式目录树并返回结果。如果是LINQ to SQL的Provider,则它会负责把表达式目录树翻译为T-SQL语句并并传递给数据库服务器,并返回最后的执行的结果;如果是一个Web Service的Provider,则它会负责翻译表达式目录树并调用Web Service,最终返回结果。
这里四个方法其实就两个操作CreateQuery和Execute(分别有泛型和非泛型),CreateQuery方法用于构造一个 IQueryable 对象,该对象可计算指定表达式目录树所表示的查询,返回的结果是一个可枚举的类型,;而Execute执行指定表达式目录树所表示的查询,返回的结果是一个单一值。自定义一个最简单的LINQ Provider,至少需要实现IQueryable和IQueryProvider两个接口,在下篇文章中,你将看到一个综合的实例。

扩展LINQ的两种方式

通过前面的讲解,我们可以想到,对于LINQ的扩展有两种方式,一是借助于LINQ to Objects,如果我们所做的查询直接在.NET代码中执行,就可以实现IEnumerable接口,而无须再去实现IQueryable并编写自定义的LINQ Provider,如.NET中内置的List等。如我们可以编写一段简单自定义代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyData<T> : IEnumerable<T>
where T : class
{
public IEnumerator<T> GetEnumerator()
{
return null;
}

IEnumerator IEnumerable.GetEnumerator()
{
return null;
}

// 其它成员
}

第二种扩展LINQ的方式当然就是自定义LINQ Provider了,我们需要实现IQueryable和IQueryProvider两个接口,下面先给出一段简单的示意代码,在下一篇中我们将完整的来实现一个LINQ Provider。如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public class QueryableData<TData> : IQueryable<TData>
{
public QueryableData()
{
Provider = new TerryQueryProvider();
Expression = Expression.Constant(this);
}

public QueryableData(TerryQueryProvider provider,
Expression expression)
{
if (provider == null)
{
throw new ArgumentNullException("provider");
}

if (expression == null)
{
throw new ArgumentNullException("expression");
}

if (!typeof(IQueryable<TData>).IsAssignableFrom(expression.Type))
{
throw new ArgumentOutOfRangeException("expression");
}

Provider = provider;
Expression = expression;
}

public IQueryProvider Provider { get; private set; }
public Expression Expression { get; private set; }

public Type ElementType
{
get { return typeof(TData); }
}

public IEnumerator<TData> GetEnumerator()
{
return (Provider.Execute<IEnumerable<TData>>(Expression)).GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return (Provider.Execute<IEnumerable>(Expression)).GetEnumerator();
}
}

public class TerryQueryProvider : IQueryProvider
{
public IQueryable CreateQuery(Expression expression)
{
Type elementType = TypeSystem.GetElementType(expression.Type);
try
{
return (IQueryable)Activator.CreateInstance(
typeof(QueryableData<>).MakeGenericType(elementType),
new object[] { this, expression });
}
catch
{
throw new Exception();
}
}

public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
{
return new QueryableData<TResult>(this, expression);
}

public object Execute(Expression expression)
{
// ......
}

public TResult Execute<TResult>(Expression expression)
{
// ......
}
}

上面这两个接口都没有完成,这里只是示意性的代码,如果实现了这两个接口,我们就可以像下面这样使用了(当然这样的使用是没有意义的,这里只是为了演示):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main(string[] args)
{
QueryableData<String> mydata = new QueryableData<String> {
"TerryLee",
"Cnblogs",
"Dingxue"
};

var result = from d in mydata
select d;
foreach (String item in result)
{
Console.WriteLine(item);
}
}

现在再来分析一下这个执行过程,首先是实例化QueryableData,同时也会实例化TerryQueryProvider;当执行查询表达式的时候,会调用TerryQueryProvider中的CreateQuery方法,来构造表达式目录树,此时查询并不会被真正执行(即延迟加载),只有当我们调用GetEnumerator方法,上例中的foreach,此时会调用TerryQueryProvider中的Execute方法,此时查询才会被真正执行,如下图所示:

执行过程