在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. ArcGis 10+Oracle发布WFS-T服务,无法更新Feature的解决方法

    现象: 前端采用Openlayers,更新Feature时服务器端返回的XML提示更新错误 原因: 参考:http://support.esri.com/en/knowledgebase/techar ...

  2. Android中Spanner获取选中内容和选中位置,根据位置选择对象

    作为一名菜鸟,关于spanner获取选中的内容文字代码,网上后很多 但是根据给出的位置来自动选择对象,这个代码一直没找到 后来找人问了问,才知道就一句话的事,特意在这里记录下 array.xml  X ...

  3. java基础疑难点总结之成员变量的继承,方法重载与重写的区别,多态与动态绑定

    1.成员变量的继承 1.1要点 子类用extends关键字继承父类.子类中可以提供新的方法覆盖父类中的方法.子类中的方法不能直接访问父类中的私有域,子类可以用super关键字调用父类中的方法.在子类中 ...

  4. 频谱分析仪 RBW&VBW

    扫频式频谱分析仪的结构如下图 RBW(Resolution Bandwidth)的影响 The RBW dictates the resolution bandwidth, which is rela ...

  5. 《TCP/IP详解 卷一》读书笔记-----动态路由协议

    1.以下条件只要有一个不满足,则需要使用动态路由协议:1)网络规模小,2)只有一个连接点用于连接其他网络,3)没有冗余的路由器(一般用作备份) 2.所谓动态路由就是各个路由器与自己相邻的路由器交换各自 ...

  6. 如何对ZBrush中面部进行快速布线

    面部布线的最重要目的是为了表情动画.人物内心的各种不同的心里活动,主要是通过面部表情反映出来.而面部变化最丰富的地方是眼部(眉毛)和口 部,其他部位则相应的会受这两部分的影响而变化.对于面部表情,必须 ...

  7. 024医疗项目-模块二:药品目录的导入导出-HSSF导入类的学习

    我们之前学习了怎么把数据的数据导出来保存到Excle中,这篇文章我们学习怎么Excel数据导出然后插入到数据库中. 我们先学习HSSF怎么用来导出数据. 看官方教程步骤如下: 第一步: 创建一个wor ...

  8. 007医疗项目-模块一:用户的查找:3.用户表查询的Action和Service

    这里主要写Action和Service. 先写Service层: 架构如下:

  9. Myeclipse+maven时Tomcat部署时maven的依赖文件不能部署到Tomcat上

    解决办法:

  10. linux64需要增加的依赖库

    sudo apt-get install git-core gnupg flex bison gperf build-essential \ zip curl zlib1g-dev gcc-multi ...