LINQ浅析

在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 = 28},
                new Student{Name = "Will", Age = 25},
                new Student{Name = "July", Age = 23},
                new Student{Name = "Lucy", Age = 24},
                new Student{Name = "Jean", Age = 22},
            };

            var stus = school.Where(s => s.Age >= 24).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 >= 24).Select(s => s.Name);

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

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

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

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

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

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

var stus = from s in school
           where s.Age >= 24
           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", 1, 2, 3};
    //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[1]);
                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 > 2012
                         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有很多操作符,这些操作符的使用就不介绍了,请自己在园子中搜搜吧。

时间: 2024-11-04 16:44:27

LINQ浅析的相关文章

LINQ内部执行原理浅析

C#3.0 增加LINQ的特性 一.基本概念 LINQ,语言级集成查询(Language INtegrated Query) 经过了最近 20 年,面向对象编程技术( object-oriented (OO) programming technologies )在工业领域的应用已经进入了一个稳定的发展阶段.程序员现在都已经认同像类(classes).对象(objects).方法(methods)这样的语言特性.考察现在和下一代的技术,一个新的编程技术的重大挑战开始呈现出来,即面向对象技术诞生以来

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

在C#中,自从有了Linq查询表达式后,程序员对可被迭代的序列或列表执行一系列的筛选.排序.过滤.分组.查询等操作.本文章所要讲述的是group关键字. Linq查询表达式,是以from关键字开头,以select或group关键字结尾,它们之中可以插入where.orderby.join.let甚至附加的from子句. group子句返回的是一个IGrouping<TKey,TElement>对象序列,请注意,是对象序列,而不是单个对象.由于group查询产生的IGrouping<TKe

c# 线程浅析(代理 、Invoke、Lock)

前言:本来想根据自己的经验总结一下c#线程相关的知识点, 写之前看了一些其他人的博客,发现自己也就掌握了不到三分之一....希望通过这次的博客将自己的知识点补充一下,写出更直白的博客和初学者分享. 这是我参考的博客地址:http://www.cnblogs.com/miniwiki/archive/2010/06/18/1760540.html  . 这个是他参考的英文原著地址:http://www.albahari.com/threading/ 原博客介绍的可以说深入浅出,鞭辟入里.不过我想写

C#中ref和out的区别浅析

这篇文章主要介绍了C#中ref和out的区别浅析,当一个方法需要返回多个值的时候,就需要用到ref和out,那么这两个方法区别在哪儿呢,需要的朋友可以参考下 在C#中通过使用方法来获取返回值时,通常只能得到一个返回值.因此,当一个方法需要返回多个值的时候,就需要用到ref和out,那么这两个方法区别在哪儿呢? MSDN:       ref 关键字使参数按引用传递.其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中.若要使用 ref 参数,则方法定义和调用方法都必

Reactive Extensions(Rx)并发浅析

Reactive Extensions(Rx)并发浅析 iSun Design & Code .Net并行编程 - Reactive Extensions(Rx)并发浅析 关于Reactive Extensions(Rx) 关于Reactive Extensions(Rx),先来看一下来自微软的官方描述: The Reactive Extensions (Rx) is a library for composing asynchronous and event-based programs us

浅析三层架构

三层架构已经学习了一段时间,机房收费系统的重构也正在进行,关于三层的认识正在不断加深,对于三层架构,我也简单谈谈我的认识! 什么是? 顾名思义,将一个软件系统的业务应用分为了三层,分别是:表现层(UI).业务逻辑层(BLL).数据访问层(DAL) 为什么? 高内聚,低耦合 怎么用? 表现层(UI,user interface) 作用:用户使用的界面,向用户展现特定业务数据,采集用户的输入信息和操作 设计原则:用户至上,兼顾简洁 常用的技术:Windows Form :Form .Control:

转载:C#中ref和out的区别浅析

这篇文章主要介绍了C#中ref和out的区别浅析,当一个方法需要返回多个值的时候,就需要用到ref和out,那么这两个方法区别在哪儿呢,需要的朋友可以参考下 在C#中通过使用方法来获取返回值时,通常只能得到一个返回值.因此,当一个方法需要返回多个值的时候,就需要用到ref和out,那么这两个方法区别在哪儿呢? MSDN:        ref 关键字使参数按引用传递.其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中.若要使用 ref 参数,则方法定义和调用方法都

浅析C# 异步编程的两种方式

一.传统BeginInvoke方式. BeginInvoke方法用于启动c#异步调用.它返回IasyncResult,可用于监视调用进度.EndInvoke方法用于检索c#异步调用结果. 调用BeginInvoke后可随时调用EndInvoke方法;如果C#异步调用未完成,EndInvoke将一直阻塞到C#异步调用完成. 总结其使用大体分5个步骤: 1.声明委拖 2.创建异步方法 3.实例化委拖(把委拖与方法关联)  A 4.通过实例的BeginInvoke调用异步方法 5.通过实例的EndIn

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

问题引出 这视乎是个完全不必要进行讨论的话题,因为linq(这里具体是linq to objects)本来就是针对集合类型的,数组类型作为集合类型的一种当然可以使用了.不过我还是想写一下,这个问题源于qq群里一位朋友的提问:.net的数组类型都隐式继承了Array类,该类是一个抽象类,并且实现了IEnumerable.ICollection.IList接口.但linq的方法都是针对实现了IEnumerable<T>泛型接口的,Array类并没有实现这些泛型接口,为什么可以使用这些方法呢? li