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有很多操作符,这些操作符的使用就不介绍了,请自己在园子中搜搜吧。

 

posted @ 2015-03-10 21:56  田小计划  阅读(2028)  评论(1编辑  收藏  举报
Fork me on GitHub