LINQ入门

语言集成查询(Language Integrated Query,LINQ),发音"link",是 .NET Framework 3.5的新特性,其全称是 Language Integrated Query,即语言集成查询,是指将查询功能和语言结合起来。从而为我们提供一种统一的方式,让我们能在C#或VB.NET语言中直接查询和操作各种数据。

LINQ

LINQ 提供了一条更常规的途径即给 .Net Framework添加一些可以应用于所有信息源( all sources of information )的具有多种用途( general-purpose )的语法查询特性( query facilities ),这是比向开发语言和运行时( runtime )添加一些关系数据( relational )特性或者类似 XML 特性( XML-specific )更好的方式。这些语法特性就叫做 .NET Language Integrated Query (LINQ) 。

LINQ除了提供一个统一的API来操作各种数据,并且为我们提供了编译时类型检查和动态创建查询表达式的能力。

匿名类型

匿名类型(anonymous type)经常用于LINQ查询的结果之中。
如下代码给出了一个创建和使用匿名类型的示例。可以通过赋值形式,简单标识符和成员访问表达式来对匿名类型进行对象初始化。另外,在WriteLine语句中,可以像访问具名类型的成员那样访问实例的成员。

class Other
{
    static public string Name = "Mary Jones";
}

class Program
{
    static void Main()
    {
        string Major = "History";
        var student = new { Age = 19, Other.Name, Major };//赋值形式,简单标识符和成员访问表达式
        Console.WriteLine("{0}, Age {1}, Major {2}", student.Name, student.Age, student.Major);
    }
}

方法语法和查询语法

书写LINQ查询时有两种语法可供选择:方法语法(Fluent Syntax)和查询表达式(Query Expression)。

  • 方法语法(method syntax)使用标准的方法调用。着这些方法是一组叫做标准查询运算符的方法,是命令式(imperative)的。
  • 查询语法(query syntax)看上去和SQL语句很相似,使用查询表达式形式书写,是声明式(declarative)的。
int[] numbers = { 2, 5, 28, 31, 17, 16, 42 };
var numsQuery = from n in numbers     //查询语法
                where n < 20
                select n;
var numsMethod = numbers.Where(x => x < 20); //方法语法
int numsCount = (from n in numbers
                 where n < 20
                 select n).Count();     //两种形式的组合

LINQ查询可以返回两种类型的姐结果--枚举或者是标量。其重要区别在于查询执行的时间。枚举处理时才执行(延迟执行),数据改动则会改变,标量则会立即执行查询表达式。

延迟执行带来的一个影响是,当我们重复遍历查询结果时,查询会被重复执行。这个时候,我们就可以利用转换运算符,如ToArray、ToList来避开重复执行,ToArray把查询结果保存至一个Array,而ToList把结果保存至泛型List<>

查询表达式

查询语法通常来讲,是创建LINQ查询的更加快捷的方式。尽管通过查询语法写出的查询比较类似于SQL查询,但实际上查询表达式的产生并不是建立在SQL之上,而是建立在函数式编程语言如LISP和Haskell中的list comprehensions(列表解析)功能之上。

查询表达式

查询表达式由查询体后的from子句组成。其中from子句和select ... group 子句是必需的。

var groupA = new[] { 3, 4, 5, 6 };
var groupB = new[] { 6, 7, 8, 9 };
var someInts = from a in groupA
               from b in groupB
               let sum = a + b
               where sum == 12
               where a >= 4
               orderby b
               select new { a, b, sum };

var students = new[]
{
    new { LName="Jones", FName="Mary", Age=19, Mahor="History"},
    new { LName="Smith", FName="Bob", Age=20, Mahor="CompSci"},
    new { LName="Fleming", FName="Carol", Age=21, Mahor="History"},
};
var query = from stu in students
            group stu by stu.Mahor;
foreach (var s in query)
{
    Console.WriteLine(s.Key);
}

标准查询运算符

标准查询运算符由一系列API方法组成,它能让我们查询任何.NET数组或集合,并且使用方法语法。共有47个标准查询运算符,可以用来操作一个或多个序列。序列是指实现了IEumerable<>接口的类,包括List<>,Dictionary<>,Stack<>,Array等。

string[] newnames = { "Tom", "Dick", "Harry", "Mary", "Jay" };
IEnumerable<string> newquery = newnames
    .Where(n => n.Contains("a"))
    .OrderBy(n => n.Length)
    .Select(n => n.ToUpper());
foreach (string name in newquery) Console.WriteLine(name);

int[] seq1 = { 1, 2, 2, 3 };
int[] seq2 = { 3, 4, 5 };
IEnumerable<int> concat = seq1.Concat(seq2);    // { 1, 2, 2, 3, 3, 4, 5 }
IEnumerable<int> union = seq1.Union(seq2);      // { 1, 2, 3, 4, 5 }

子查询、创建策略和数据转换

在创建一个复杂的查询时,通常我们需要用到子查询。相信大家都记得SQL查询里的子查询,在创建LINQ查询时也是如此。在LINQ中,对于方法语法,一个子查询包含在另外一个查询的lambda表达式中,对于查询表达式语法来讲,所有不是from子句中引用的查询都是子查询。

static void TestSubQuery()
{
    string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
    // 获取所有长度最短的名字(注意:可能有多个)
    IEnumerable<string> outQuery = names
        .Where(n => n.Length == names     
            .OrderBy(n2 => n2.Length)
            .Select(n2 => n2.Length).First());      // Tom, Jay"

    // 与上面方法语法等价的查询表达式
    IEnumerable<string> outQuery2 =
        from n in names
        where n.Length ==            
            (from n2 in names orderby n2.Length select n2.Length).First()
        select n;

    // 我们可以使用Min查询运算符来简化
    IEnumerable<string> outQuery3 =
        from n in names
        where n.Length == names.Min(n2 => n2.Length)
        select n;

    //可以把子查询分离出来对让它只执行一次
    int shortest = names.Min(n => n.Length);
    IEnumerable<string> query = from n in names
                                where n.Length == shortest
                                select n;
}

创建复杂LINQ查询的创建策略有三种:

  • 渐进式创建查询,就是通过链接查询运算符的方式来创建LINQ查询。因为每一个查询运算符返回一个装饰者sequence,所以我们可以在其之上继续调用其它查询运算符。
  • into关键字,是对分步构建查询表达式的一种简写方式。
  • 包装查询,可以通过在一个查询中嵌入另一个查询来改写,这样可以把多个查询组合成单个查询。

考虑如下的例子:我们需要在名字列表中去除所有名字的元音字母,然后对长度大于2的名字进行排序。

// 渐进式查询(Progressive query building)
IEnumerable<string> query4 =
    from n in names
    select Regex.Replace(n, "[aeiou]", "");

query = from n in query where n.Length > 2 orderby n select n;

// into关键词
IEnumerable<string> query5 = from n in names
                             select n.Replace("a", "").Replace("e", "").Replace("i", "")
                                     .Replace("o", "").Replace("u", "")
                             into noVowel
                             where noVowel.Length > 2
                             orderby noVowel
                             select noVowel;   // Result: Dck, Hrry, Mry


// 用包装查询方式进行改写(Wrapping Queries)
IEnumerable<string> query6 =
    from n1 in
        (
            from n2 in names
            select Regex.Replace(n2, "[aeiou]", "")
        )
    where n1.Length > 2
    orderby n1
    select n1;

// 与上面等价的方法语法
IEnumerable<string> query7 = names
    .Select(n => Regex.Replace(n, "[aeiou]", ""))
    .Where(n => n.Length > 2)
    .OrderBy(n => n);

// let关键字
var query8 = from n in names
            let Vowelless = Regex.Replace(n, "[aeiou]", "")
            where Vowelless.Length > 2
            select n;   //正是因为使用了let,此时n仍然可见

扩展方法

System.Linq.Enumerable类声明了标准查询运算符方法,即扩展了IEumerable泛型类的扩展方法。在LINQ表达式中,正是扩展方法让LINQ查询运算符的链接成为了可能。因为运算符本身是对IEnumerable类型的扩展,并且返回IEnumerable类型的结果。我们可以比较一下使用扩展方法和使用静态方法的区别,结果会一目了然。扩展方法非常自然地反映了从左到右的数据流向,同时保持lambda表达式与查询运算符的位置一致性。

string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
//   extension methods make LINQ elegant
IEnumerable<string> query1 = names
    .Where(n => n.Contains("a"))
    .OrderBy(n => n.Length)
    .Select(n => n.ToUpper());

//   static methods lose query's fluency
IEnumerable<string> query2 =
    Enumerable.Select(
        Enumerable.OrderBy(
            Enumerable.Where(names, n => n.Contains("a")
            ), n => n.Length
        ), n => n.ToUpper()
    );

将委托作为参数

LINQ定义了两套泛型委托类型与标准查询运算符一起使用,即Func委托和Action委托,各有17个成员。我们用做实参的委托对象必须是这些类型或这些形式之一。TR代表返回值,并且总是在类型参数列表中的最后一个。
在这里列出了前4个泛型Func委托。第一个没有方法参数,返回符合返回类型的对象。第二个接受单个方法参数并且返回一个值,依次类推。

public delegate TR Func<out TR>();
public delegate TR Func<in T1,out TR>(Tla1);
public delegate TR Func<in T1,in T2,out TR>(T1 al,T2 a2);
public delegate TR Func<in T1,in T2,in T3,out TR>( T1 al,T2 a2,T3 a3);

返回类型类型参数方法参数注意返回类型参数有一个out关键字,使之可以协变,也就是说可以接受声明的类型或从这个类型派生的类型。输人参数有一个in关键字,使之可以逆变,也就是你可以接受声明的类型或从这个类型派生的类型。
知道了这些,如果我们再来看一下Count的声明。我们可以发现第二个参数必须是委托对象,它接受单个T类型的参数作为方法参数并且返回一个bool类型的值。

public static int Count<T>(this IEumerable<T> source,
                                Func<T, bool>predicate);

参考:
LINQ之路
LINQ 查询简介 (C#)

posted @ 2020-10-07 16:02  Jamest  阅读(131)  评论(0编辑  收藏  举报