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

  • 相关阅读:
    (转)python3 计算字符串、文件md5值
    CF1398D Colored Rectangles(DP)
    Colab运行GitHub代码
    Pytorch错误解决
    SAP内部订单
    料工费
    摘抄,泛起内心的一丝波澜
    审计意见类型
    生产订单设置自动倒冲
    SAP常用事务码及规则tcode
  • 原文地址:https://www.cnblogs.com/wilber2013/p/4328402.html
Copyright © 2011-2022 走看看