最近想研究如何自定义 LINQ Provider ,但是一直无法入手,先写点收获吧~
MSDN 上的这篇文章(《启用数据源以进行 LINQ 查询》)中写到:
如果想对自己的数据源进行 LINQ 查询,那必须使用一下四种方法的其中一种。
看到其中第二条,让人心生疑惑,那下面就来探讨一下吧~
1
2
3
|
var queryLondonCustomers = from cust
in
customers
where cust.City ==
"London"
select cust;
|
很简单的几行代码,这就是 LINQ,这里有几个关键字: from, in, where, select …
这几个是新增的关键字,还有别的好多个~
关键看这里的 customers ,这里就是一个可以被 LINQ 查询的对象。
按照一般的思路,它一定是继承了某个接口,所以可以用 LINQ 查询。
在学习 LINQ 的时候我们常常会看到两个接口:IEnumerable<T> 和 IQueryable<T> ,难道是这两个?
备注:这两个接口的区别是,前者提供的是对内存中数据的查询,后者提供的是对远程数据的查询,这里就不展开了。
如果你没有深入了解,恐怕你就会认为的确如此了。
这里有两个疑点:
让我们继续探讨吧!
一段代码
1
2
3
4
5
6
7
8
9
10
|
class
Program
{
static
void
Main(
string
[] args)
{
var data = Enumerable.Range(1, 5).ToArray();
var q = from _
in
data
where _ > 3
select _;
}
}
|
让我们看看它的 IL 代码(部分)
1
2
3
4
|
IL_0029: ldsfld
class
[mscorlib]System.Func`2<int32,
bool
> ConsoleApplication1.Program::
'CS$<>9__CachedAnonymousMethodDelegate1'
IL_002e: call
class
[mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<int32>(
class
[mscorlib]System.Collections.Generic.IEnumerable`1<!!0>,
class
[mscorlib]System.Func`2<!!0,
bool
>)
IL_0033: stloc.1
IL_0034: ret
|
关键代码:[System.Core]System.Linq.Enumerable::Where<int32>
让我们找到它的出处
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
namespace
System.Linq
{
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"
);
if
(source
is
Iterator<TSource>)
return
((Iterator<TSource>)source).Where(predicate);
if
(source
is
TSource[])
return
new
WhereArrayIterator<TSource>((TSource[])source, predicate);
if
(source
is
List<TSource>)
return
new
WhereListIterator<TSource>((List<TSource>)source, predicate);
return
new
WhereEnumerableIterator<TSource>(source, predicate);
}
}
}
|
其实,它最终只是调用了 IEnumerable<T> 的一个扩展方法,难怪别人总说上面的代码其实等效于这个
1
2
3
4
5
|
var data = Enumerable.Range(1, 5).ToArray();
var q = from _
in
data
where _ > 3
select _;
var q2 = data.Where(_ => _ > 3);
//等效于上面一行代码
|
让我们来理一下思路:
为了验证上面的是否正确,那就让我们来动手实践一下吧!
按照刚才分析的,是不是只要给自己的类型提供几个方法就行了呢?
动手吧!
我们先来实现 select 和 where 关键字吧~
先看看 .net 源码中的函数吧,这样我们才能知道这个函数需要传入什么,需要返回什么。
1
2
3
4
5
6
7
8
9
10
|
namespace
System.Linq
{
public
static
class
Enumerable
{
public
static
IEnumerable<TSource> Where<TSource>(
this
IEnumerable<TSource> source, Func<TSource,
bool
> predicate) {
}
public
static
IEnumerable<TResult> Select<TSource, TResult>(
this
IEnumerable<TSource> source, Func<TSource, TResult> selector) {
}
}
}
|
这里不用看具体的实现,只要看传入的参数和返回类型就行了。
那么接下来就让我们写自己的类型吧!
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
|
class
Program
{
static
void
Main(
string
[] args)
{
var mySchool =
new
School();
var q = from _
in
mySchool
where _.Contains(
"a"
)
select _;
}
}
class
School
{
protected
List<
string
> Student {
get
;
set
; }
public
School()
{
//生成一个 Student 序列
Student =
new
List<
string
>
{
"abc"
,
"xyz"
,
"123"
};
}
public
School Where(Func<
string
,
bool
> predicate)
{
//这里对List有增删了,所以不能直接用foreach,删除不满足条件的学生
for
(var k = 0; k < Student.Count; k++)
{
if
(predicate(Student[k]))
continue
;
Student.RemoveAt(k);
k--;
}
return
this
;
}
public
School Select(Func<School, School> selector)
{
//这里就不实现了
return
this
;
}
}
|
这里的场景:
School 类型,并不是什么枚举类型,但是需要用 where 做一些过滤,用 order 做一些排序,里面的数据呢,是不暴露出来的。
所以如果不继承 IEnumerable<T> 或 IQueryable<T> 的接口话,只要模仿它们的扩展方法,实现一些基本的方法即可。
具体的实现貌似没找到什么好的资料,只能自己慢慢摸索了~ 这其中还有一些泛型,可以根据自己的需求做相应的修改!
从上面的种种迹象表明,LINQ 应该是编译器级别的东西,而不是 .net CLR 级的东西(这个仅仅是个人猜测)
写完了这些,我一直在想微软为什么要真么做?
按照一般的设计思路,理想状态下,应该有一个统一的接口来约束,然后所有继承这个接口的类型都可以进行 LINQ 查询。
但是为什么没这么做呢?
个人分析:
微软想要实现 Ling to everything,所以要让一切数据支持 LINQ,一般可以认为实现 IEnumerable<T> 接口的类型都是数据,也就是说要让所有实现 IEnumerable<T> 接口的类型都可以实现 LINQ 查询。
但是这是一个接口,不是一个抽象类,不能写具体的方法。LINQ 查询只要用的 IEnumerable<T> 接口所提供的函数就行了。
所以扩展方法是一个非常明智的选择!
那 IQueryable<T> 是怎么回事呢?IQueryable<T> 中包含一个 IQueryProvider 类型,它可以将本地的查询转换为远程的查询,忽略内部实现,你可以认为 IEnumerable<T> 和 IQueryable<T>是一样的。
不继承 IEnumerable<T> 的就不是数据了吗?恐怕不是吧~
不是说 Ling to everything 吗?为什么不对 Object 写 LINQ 查询呢?
首先不是所有的 Object 都是数据,另外 Object 也没有可以依赖的方法供数据查询用。
所以如果是你自己的类型,并且不实现 IEnumerable<T> 或 IQueryable<T> 接口,那么你只要自己写一些函数就行了!
而且可以自由地控制,例如:如果排序方法对你的类型来说没有意义,那你可以不写它。
虽然我还是觉得这样的设计略微有点不严谨,但它的确很方便~
文章同时发布在了我的个人博客:不继承 IEnumerable