在C# 3.0之前,我们对不同的数据源(数据集合、SQL 数据库、XML 文档等等)进行操作(查询、筛选、投影等等),会使用不同的操作方式。

C# 3.0中提出了LINQ(Language Integrated Query)这个非常重要的特性, LINQ定义了一组标准查询操作符用于在所有基于.NET平台的编程语言中更加直接地声明跨越、过滤和投射操作的统一方式。

关于LINQ中标准操作符的介绍和使用,园子里有很多很好的文章了,所以这里就不介绍LINQ的操作符使用了,主要通过一些概念和例子介绍LINQ是怎么工作的。

LINQ to Objects

首先我们看看LINQ to Objects:

  1. LINQ to Objects是指直接对任意实现 IEnumerable 或 IEnumerable<T> 接口集合使用 LINQ 查询
  2. Enumerable静态类封装了对查询IEnumerable或 IEnumerable<T>接口类型的静态扩展方法
  3. 从Enumerable类的代码可以看到所有的扩展方法中的逻辑表达式都是Func泛型委托,也就是直接使用委托去执行逻辑操作

从上面的概况可以看到,对于实现IEnumerable 或 IEnumerable<T> 接口的集合,我们都可以使用Enumerable中的扩展方法对集合使用LINQ查询。

为了进一步理解这些概念,下面例子中创建了Where和Select扩展方法,模拟了Enumerable中的标准操作符Where和Select:

namespace LINQtoObject
{
public static class DummyLINQ
{
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
if (source == null || predicate == null)
{
throw new ArgumentNullException();
}
return WhereImpl<T>(source, predicate);
} public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
if (source == null || selector == null)
{
throw new ArgumentNullException();
}
return SelectImpl<TSource, TResult>(source, selector);
} private static IEnumerable<T> WhereImpl<T>(IEnumerable<T> source, Func<T, bool> predicate)
{
foreach (var item in source)
{
if (predicate(item))
{
Console.WriteLine(" Where: {0} matches where", item);
yield return item;
}
else
{
Console.WriteLine(" Where: {0} doesn't match where", item);
}
}
} private static IEnumerable<TResult> SelectImpl<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
foreach (var item in source)
{
Console.WriteLine(" Selcet: select {0}", item);
yield return selector(item);
}
}
} class Student
{
public string Name { get; set; }
public int Age { get; set; } public override string ToString()
{
return String.Format("(Name:{0}, Age:{1})", this.Name, this.Age);
}
} class Program
{
static void Main(string[] args)
{
List<Student> school = new List<Student>
{
new Student{Name = "Wilber", Age = },
new Student{Name = "Will", Age = },
new Student{Name = "July", Age = },
new Student{Name = "Lucy", Age = },
new Student{Name = "Jean", Age = },
}; var stus = school.Where(s => s.Age >= ).Select(s => s.Name);
foreach (var stu in stus)
{
Console.WriteLine(stu);
} Console.Read();
}
}
}

代码的输出为:

上面的代码简单的模拟了LINQ to Objects的工作方式及实现,自定义的Where和Select操作符也按照了我们预期的方式工作。

通过IL代码可以看到,在LINQ to Objects中,所有扩展方法中的逻辑表达式都被编译器转换成了匿名方法。

延迟执行

如果通过单步调试查看上面代码,可以看到当下面语句执行的时候,并没有处理任何数据。

var stus = school.Where(s => s.Age >= ).Select(s => s.Name);

只有当访问结果IEnumerable<Student>的时候,上面的查询才会被真正的执行,这个就是LINQ的延迟执行,其实这个延迟执行是基于我们前面介绍过的yield return创建的迭代器块。

虽然我们看到了前面例子的输出,但是下面的序列图可以更加清楚的表示上面查询执行的过程:

方法语法(Fluent Syntax)和查询表达式(Query Expression)

有了Enumerable静态类中所有扩展方法的支持,对于所有实现IEnumerable或 IEnumerable<T>接口的集合,我们可以通过扩展方法组成的方法链完成集合的操作。

var stus = school.Where(s => s.Age >= ).Select(s => s.Name);

在LINQ中,我们还可以通过查询表达是的方式完成LINQ查询:

var stus = from s in school
where s.Age >=
select s.Name;

CLR并不具有查询表达式的概念,编译器会在程序编译时把查询表达式转换为方法语法,从而进行扩展方法的调用。

OfType 和Cast

前面例子中我们使用的是List<T>集合,当我们对弱类型的集合使用LINQ的时候,我们就需要用到OfType和Cast两个操作符了。

  • OfType 和Cast共同点:
    • 都可以处理任意非类型化的序列,并返回强类型的序列
  • 不同点:
    • Cast把每个元素都转换成目标类型(遇到类型转换错误的时候,就会抛出异常)
    • OfType会跳过错误类型的元素,然后对序列进行转换

下面看一个简单的例子:

static void Main(string[] args)
{
ArrayList list = new ArrayList { "Frist", "Second", "Third" };
IEnumerable<string> stringList1 = list.Cast<string>();
foreach (var str in stringList1)
{
Console.WriteLine(str);
} IEnumerable<string> stringList2 = list.OfType<string>();
foreach (var str in stringList2)
{
Console.WriteLine(str);
} list = new ArrayList { "Frist", "Second", "Third", , , };
//IEnumerable<string> stringList3 = list.Cast<string>();
//foreach (var str in stringList3)
//{
// Console.WriteLine(str);
//} IEnumerable<int> stringList4 = list.OfType<int>();
foreach (var str in stringList4)
{
Console.WriteLine(str);
} Console.Read();
}

注释掉的代码会产生异常,因为对ArrayList中的int类型数据进行Cast<string>时候会失败。

LINQ to Others

前面看到,对于实现IEnumerable 或 IEnumerable<T> 接口的集合,我们可以直接使用LINQ to Objects。那么当我们碰到其他数据源的时候,我们就需要使用这种数据源特有的LINQ查询了。

如果要对特殊的数据源实现LINQ查询,我们需要知道Queryable类,以及IQueryable<T>和IQueryableProvider<T>接口。

IQueryable<T>和IQueryableProvider<T>

从下面的类图可以看到,IQueryable<T>继承自IEnumerable,IEnumerable<T>和非泛型的IQueryable。

对于IQueryable仅有三个属性:Provider、ElementType和Expression。

对于一个特殊的数据源,如果我们想要使用LINQ进行数据源查询,我们就要自己实现IQueryable<T>、IQueryProvider两个接口的。当使用LINQ查询表达式来查询System.Linq.IQueryable<T>类型对象的话,编辑器会认为我们要查询自定的数据源对象,在执行的时候会调用我们实现的System.Linq.IQueryableProvider<T>接口实现类,该类提供对表达式树的解析和执行。

自己实现的provider把表达式树转换成目标平台的特定类型的查询语句,例如LINQ to SQL的provider就是把表达式树转换成SQL语句,然后有SQL server执行SQL语句返回结果。

Queryable

LINQ to Objects中的数据源总是实现IEnumerable<T>(可能在调用OfType或Cast之后),然后使用Enumerable类中的扩展方法。

类似的,对于实现了IQueryable<T>的数据源,就可以使用Queryable静态类(在System.Linq命名空间中)中的扩展方法。

如果查看代码会发现,System.Linq.Queryable静态类中的所有扩展方法与System.Linq.Enumerable类中的扩展方法的区别是所有的Func类型都被System.Linq.Expressions.Expression<T>类型包装着,也就是说我们的查询表达式(Lambda表达式,匿名方法)在LINQ to Objects中转换成了Func类型的委托,在LINQ to Others中转换成了表达式树。

有了Queryable类中的扩展方法,相当于自定义的数据源也有了统一的操作符,也就是说不同的数据源也可以使用统一的查询方式。当我们使用ILSpy查看"Where"操作符的代码,可以发现这写扩展方法都使用了我们实现的provider。

下面看一个简单的例子,假设我们有一个图书系统,可以通过特定类型的方式(QueryString)进行检索。如果我们想要通过LINQ实现对这种数据源的查询,我们就需要实现一个porvider可以把LINQ查询转换成QueryString。

namespace LINQtoOthers
{
public class QueryableSource<T> : IQueryable<T>
{
public IQueryProvider Provider { get; private set; }
public Expression Expression { get; private set; }
public Type ElementType
{
get { return typeof(T); }
} public QueryableSource()
{
Provider = new QueryableSourceProvider();
Expression = Expression.Constant(this);
} public QueryableSource(QueryableSourceProvider provider, Expression expression)
{
if (provider == null)
{
throw new ArgumentNullException("provider");
} if (expression == null)
{
throw new ArgumentNullException("expression");
} if (!typeof(IQueryable<T>).IsAssignableFrom(expression.Type))
{
throw new ArgumentOutOfRangeException("expression");
} Provider = provider;
Expression = expression;
} public IEnumerator<T> GetEnumerator()
{
return (Provider.Execute<IEnumerable<T>>(Expression)).GetEnumerator();
} System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
} public class QueryableSourceProvider : IQueryProvider
{
public IQueryable<T> CreateQuery<T>(Expression expression)
{
return new QueryableSource<T>(this, expression);
} public IQueryable CreateQuery(Expression expression)
{
try
{
return (IQueryable)Activator.CreateInstance(
typeof(QueryableSource<>).MakeGenericType(expression.Type),
new object[] { this, expression });
}
catch
{
throw new Exception();
}
} public T Execute<T>(Expression expression)
{
BookInfoExpressionVisitor visitor = new BookInfoExpressionVisitor();
visitor.Visit(expression);
Console.WriteLine("QueryString is {0}", visitor.QueryString); return (T)((IEnumerable<BookInfo>)new List<BookInfo> { });
} public object Execute(Expression expression)
{
return null;
}
} public class BookInfoExpressionVisitor : ExpressionVisitor
{
public StringBuilder QueryString { get; set; } public BookInfoExpressionVisitor()
{
this.QueryString = new StringBuilder();
} protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.Name == "Where")
{
Console.WriteLine("parsing --- {0}", node);
this.Visit((UnaryExpression)node.Arguments[]);
return node;
} Console.WriteLine(string.Format("!!!!!Method {0} is not supported", node.Method.Name));
return node;
} protected override Expression VisitUnary(UnaryExpression node)
{
Console.WriteLine("parsing --- {0}", node);
this.Visit(node.Operand);
return node;
} protected override Expression VisitBinary(BinaryExpression node)
{
Console.WriteLine("parsing --- {0}", node);
this.Visit(node.Left);
switch (node.NodeType)
{
case ExpressionType.AndAlso:
this.QueryString.Append(" AND ");
break;
case ExpressionType.OrElse:
this.QueryString.Append(" OR ");
break;
case ExpressionType.Equal:
this.QueryString.Append(" = ");
break;
case ExpressionType.NotEqual:
this.QueryString.Append(" <> ");
break;
case ExpressionType.LessThan:
this.QueryString.Append(" < ");
break;
case ExpressionType.LessThanOrEqual:
this.QueryString.Append(" <= ");
break;
case ExpressionType.GreaterThan:
this.QueryString.Append(" > ");
break;
case ExpressionType.GreaterThanOrEqual:
this.QueryString.Append(" >= ");
break;
default:
Console.WriteLine(string.Format("!!!!!Operation type {0} is not supported", node.NodeType));
break;
}
Console.WriteLine("parsing --- {0}", node.NodeType);
this.Visit(node.Right);
return node;
} protected override Expression VisitMember(MemberExpression node)
{
Console.WriteLine("parsing --- {0}", node);
this.QueryString.Append("[" + node.Member.Name);
return node;
} protected override Expression VisitConstant(ConstantExpression node)
{
Console.WriteLine("parsing --- {0}", node);
if (node.Type.Name == "String")
{
this.QueryString.Append("\"");
this.QueryString.Append(node.Value);
this.QueryString.Append("\"]");
}
else
{
this.QueryString.Append(node.Value + "]");
}
return node;
}
} public class BookInfo
{
public string BookName { get; set; }
public string Author { get; set; }
public int SelledNumber { get; set; }
public int year { get; set; }
} class Program
{
static void Main(string[] args)
{
QueryableSource<BookInfo> queryableSource = new QueryableSource<BookInfo>(); var result = from book in queryableSource
where book.BookName == "C# in Depth" && book.year >
select book;
foreach (var item in result)
{
Console.WriteLine(item);
} Console.Read();
}
}
}

通过程序的输出可以看到provider一步步解析表达式树的过程,最终生成了一个QueryString。

总结

本文通过一些概念和例子介绍了LINQ to Objects和LINQ to Others,能够对LINQ有一些基本的认识。

C# 3.0中出现的LINQ极大程度的简化了数据操作的代码,通过LINQ实现的数据操作代码会更加的直观。同时,有了LINQ,即使是不同的数据源,我们也可以使用统一的数据操作方式。

LINQ有很多操作符,这些操作符的使用就不介绍了,请自己在园子中搜搜吧。

LINQ浅析的更多相关文章

  1. 读书笔记 C# Linq查询之group关键字浅析

    在C#中,自从有了Linq查询表达式后,程序员对可被迭代的序列或列表执行一系列的筛选.排序.过滤.分组.查询等操作.本文章所要讲述的是group关键字. Linq查询表达式,是以from关键字开头,以 ...

  2. 数组为什么可以使用linq查询

    问题引出 这视乎是个完全不必要进行讨论的话题,因为linq(这里具体是linq to objects)本来就是针对集合类型的,数组类型作为集合类型的一种当然可以使用了.不过我还是想写一下,这个问题源于 ...

  3. linq简介

    语言集成查询(Language INtegrated Query,LINQ)是一项微软技术,新增一种自然查询的SQL语法到.NET Framework的编程语言中,可支持Visual Basic .N ...

  4. Reactive Extensions(Rx)并发浅析

    Reactive Extensions(Rx)并发浅析 iSun Design & Code .Net并行编程 - Reactive Extensions(Rx)并发浅析 关于Reactive ...

  5. C#中ref和out的区别浅析

    这篇文章主要介绍了C#中ref和out的区别浅析,当一个方法需要返回多个值的时候,就需要用到ref和out,那么这两个方法区别在哪儿呢,需要的朋友可以参考下   在C#中通过使用方法来获取返回值时,通 ...

  6. 浅析Entity Framework Core2.0的日志记录与动态查询条件

    前言 Entity Framework Core 2.0更新也已经有一段时间了,园子里也有不少的文章.. 本文主要是浅析一下Entity Framework Core2.0的日志记录与动态查询条件 去 ...

  7. 浅析Entity FrameWork性能优化

    浅析EF性能优化 1.       数据Load 延迟加载:当实体第一次读取时,相关数据没有加载:当第一次试图访问导航属性时,所需的导航数据自动加载,EF默认使用这种方式加载数据,尽量使用预先加载和显 ...

  8. .net 反射访问私有变量和私有方法 如何创建C# Closure ? C# 批量生成随机密码,必须包含数字和字母,并用加密算法加密 C#中的foreach和yield 数组为什么可以使用linq查询 C#中的 具名参数 和 可选参数 显示实现接口 异步CTP(Async CTP)为什么那样工作? C#多线程基础,适合新手了解 C#加快Bitmap的访问速度 C#实现对图片文件的压

    以下为本次实践代码: using System; using System.Collections.Generic; using System.ComponentModel; using System ...

  9. c# 把一个匿名对象赋值给一个Object类型的变量后,怎么取这个变量? c# dynamic动态类型和匿名类 详解C# 匿名对象(匿名类型)、var、动态类型 dynamic 深入浅析C#中的var和dynamic

    比如有一个匿名对象,var  result =......Select( a=>new {  id=a.id, name=a.name});然后Object  obj =  result ;我怎 ...

随机推荐

  1. poj 3177 Redundant Paths

    题目链接:http://poj.org/problem?id=3177 边双连通问题,与点双连通还是有区别的!!! 题意是给你一个图(本来是连通的),问你需要加多少边,使任意两点间,都有两条边不重复的 ...

  2. Mac/Linux 定时运行命令行

    想要开机运行的话可以通过 mac 自带的 Automator 将要运行的命令打包成一个app,用后在用户组的“登录时启动”列表里加上那个app. 但是想要定时运行就不能这么做了,要用上一个叫cront ...

  3. java 字节流和字符流的区别 转载

    转载自:http://blog.csdn.net/cynhafa/article/details/6882061 java 字节流和字符流的区别 字节流与和字符流的使用非常相似,两者除了操作代码上的不 ...

  4. Visualize real-time data streams with Gnuplot

    源文地址 (September 2008) For the last couple of years, I've been working on European Space Agency (ESA) ...

  5. Scanner类总结

    1.构造器 Scanner(File source) Scanner(InputStream source) Scanner(String source) 参考:http://blog.sina.co ...

  6. UVA 439 Knight Moves --DFS or BFS

    简单搜索,我这里用的是dfs,由于棋盘只有8x8这么大,于是想到dfs应该可以过,后来由于边界的问题,TLE了,改了边界才AC. 这道题的收获就是知道了有些时候dfs没有特定的边界的时候要自己设置一个 ...

  7. MString 与 QString 互转

    MQtUtil static MString  toMString (const QString &qstr)   Convenience utility to convert a QStri ...

  8. Centos7下修改默认网卡名(改为eth0)的操作记录

    安装好centos7版本的系统后,发现默认的网卡名字有点怪,为了便于管理,可以手动修改.下面对centos7版本下网卡重命名操作做一记录:1)编辑网卡信息[root@linux-node2~]# cd ...

  9. WMI入门

    转:http://www.cnblogs.com/ceachy/archive/2013/03/21/WMI_What.html WMI入门(一):什么是WMI WMI出现至今已经二十多年了,但很多人 ...

  10. 思科产品选型pdf

    以前做工程时候想起了设备选型时候用过的一份文档. 有个小伙伴今天问起思科设备选型,恰好google到了这份文档 https://www.cisco.com/web/CN/products/pdf/04 ...