1  /  2  页   12 跳转 查看:3803

Linq简介

Linq简介

文/cobra  出处/拼吾爱  参考/Microsoft MSDN

LINQ的英文全称是:Language INtegrated Query,语言级集成查询。

二十年之后,业界在面向对象 (OO) 编程技术的发展过程中趋于稳定。现在,程序员已经认为诸如类、对象和方法等特性是理所当然的。在探究当前的和下一代技术时,明显可以看出,有关编程技术的下一个难题是降低访问和集成特定信息(这些信息不是使用 OO 技术进行原始定义的)的复杂性。非 OO 信息的两个最常见源是关系数据库和 XML。

对于 LINQ 项目,我们采取了更为普通的方法,并向 .NET Framework 中添加了适用于所有信息源(而不只是关系数据或 XML 数据)的通用查询工具,而不是在编程语言和运行库中添加相关功能或特定于 XML 的功能。该工具名为 .NET 语言集成查询 (LINQ)。

我们使用语言集成查询 这一术语表明,该查询是开发人员主要编程语言(例如,C#、Visual Basic)的集成功能。语言集成查询使得查询表达式 能够得益于丰富的元数据、编译时语法检查、静态输入和智能感知(以前只能用于命令代码)。语言集成查询还允许将单个通用的声明查询工具应用于所有内存中信息,而不只是来自外部源的信息。

.NET 语言集成查询定义了一组通用的标准查询操作符,允许在任何基于 .NET 的编程语言中通过直接的声明方式进行遍历、筛选和投影操作。标准查询操作符允许将查询应用于任何基于 IEnumerable 的信息源。LINQ 允许第三方使用特定于域的新操作符(适用于目标域或技术)来补充标准查询操作符集。更重要的是,第三方还可以使用自己提供附加服务(例如,远程计算、查询转换、优化等)的实现来自由替换标准查询操作符。通过符合 LINQ 样式 的约定,此类实现可以享受与标准查询操作符相同的语言集成和工具支持。

查询体系结构的可扩展性在 LINQ 项目本身中用于提供可同时处理 XML 和 SQL 数据的实现。处理 XML 的查询操作符 (XLinq) 使用一个高效、易于使用的内存中 XML 工具来提供宿主编程语言中的 XPath/XQuery 功能。处理关系数据的查询操作符 (DLinq) 将基于 SQL 的架构定义集成构建到 CLR 类型系统中。该集成通过关系数据提供强类型化,同时直接在底层存储中保留关系模型的表达功能和查询计算的性能。

 感谢原创者的辛勤劳动,希望对您有所帮助,转载请注明原出处。
 您可能对 [Linq] 的这些文章也感兴趣:

LINQ体验(1)——Visual Studio 2008新特性
LINQ to SQL异步查询
使用LinqExtender轻松实现自定义LINQ提供器
使用linq to xml 快速创建Rss 之二:Syndication篇
打造自己的LINQ Provider(中):IQueryable和IQueryProvider
优秀Linq网站推荐
使用linq to xml 快速创建RSS
LINQ to SQL活学活用(2):躲起来别让我看见
在LINQ to SQL中使用Translate方法以及修改查询用SQL
LINQ的分组聚合技术
 

回复:Linq简介

标准查询操作符简介
为了查看执行中的语言集成查询,我们将从一个简单的 C# 3.0 程序开始,该程序使用标准的查询操作符来处理数组的内容:

using System;
using System.Query;
using System.Collections.Generic;

class app {
  static void Main() {
    string[] names = { "Burke", "Connor", "Frank",
                      "Everett", "Albert", "George",
                      "Harris", "David" };

    IEnumerable expr = from s in names
                              where s.Length == 5
                              orderby s
                              select s.ToUpper();

    foreach (string item in expr)
      Console.WriteLine(item);
  }
}

如果您编译并运行该程序,将看到以下输出:

BURKE
DAVID
FRANK

要了解语言集成查询如何工作,我们需要剖析该程序的第一个语句。

IEnumerable expr = from s in names
                          where s.Length == 5
                          orderby s
                          select s.ToUpper();

使用一个查询表达式 初始化局部变量 expr。通过应用一个或多个标准查询操作符或特定于域的操作符,查询表达式可以操作一个或多个信息源。该表达式使用了三个标准查询操作符:Where、OrderBy 和 Select。

Visual Basic 9.0 也支持 LINQ。以下是以 Visual Basic 9.0 编写的上述语句:

Dim expr As IEnumerable(Of String) = _
                  Select s.ToUpper() _
                  From s in names _
                  Where s.Length = 5 _
                  Order By s

这里显示的 C# 和 Visual Basic 语句均使用查询语法。与 foreach 语句一样,查询语法是一个方便的声明性代码缩写,您可以手动编写它。上述语句在语义上与以下所示的以 C# 编写的显式语法完全相同:

IEnumerable expr = names
                          .Where(s => s.Length == 5)
                          .OrderBy(s => s)
                          .Select(s => s.ToUpper());

Where、OrderBy 和 Select 操作符的参数称为 λ 表达式,它们是类似于委托的代码片段。它们允许将标准查询操作符单独定义为方法,并使用点标记串连在一起。这些方法共同构成了可扩展查询语言的基础。
 

回复:Linq简介

支持 LINQ 项目的语言功能
LINQ 完全构建于通用的语言功能之上,其中某些是在 C# 3.0 和 Visual Basic 9.0 中新增的功能。每个功能都有自己的实用工具,但这些功能共同提供了一种定义查询和可查询 API 的可扩展方法。在本部分中,我们将探究这些语言功能,以及它们如何提供更为直接和声明性的查询模式。

λ 表达式和表达式树

许多查询操作符都允许用户提供执行筛选、投影或键值提取的函数。基于 λ 表达式的概念而生成的查询工具为开发人员提供了一种编写函数的简便方法,这些函数可以作为后续计算的参数进行传递。λ 表达式类似于 CLR 委托,它必须符合委托类型定义的方法签名。为了进行说明,我们可以使用 Func 委托类型将上述语句扩展为更为显式的等效形式:

Func  filter  = s => s.Length == 5;
Func extract = s => s;
Func project = s => s.ToUpper();

IEnumerable expr = names.Where(filter)
                                .OrderBy(extract)
                                .Select(project);

λ 表达式是 C# 2.0 匿名方法的自然演化结果。例如,我们可以使用匿名方法编写上述示例,如下所示:

Func  filter  = delegate (string s) {
                                  return s.Length == 5;
                              };

Func extract = delegate (string s) {
                                  return s;
                              };

Func project = delegate (string s) {
                                  return s.ToUpper();
                              };

IEnumerable expr = names.Where(filter)
                                .OrderBy(extract)
                                .Select(project);

总之,开发人员可以自由地将命名方法、匿名方法或 λ 表达式与查询操作符一起使用。λ 表达式的优点是,能够提供最直接而简洁的创作语法。更重要的是,λ 表达式可以编译为代码,也可以编译为数据,从而允许优化器、转换器和计算器在运行时处理 λ 表达式。

LINQ 定义了一个特殊类型 Expression(在 System.Expressions 命名空间中),该类型用于指示给定 λ 表达式需要表达式树,而不是基于 IL 的传统方法体。表达式树是 λ 表达式的有效内存中数据表示形式,它使表达式的结构透明且显式。

编译器是发出可执行 IL 还是表达式树取决于 λ 表达式的用法。如果将 λ 表达式指定给委托类型的变量、字段或参数,则编译器将发出与匿名方法等效的 IL。如果将 λ 表达式指定给 Expression 类型的变量、字段或参数,则编译器将发出表达式树。

例如,请考虑以下两个变量声明:

Func            f = n => n < 5;
Expression<FUNC> e = n => n < 5;

变量 f 是对委托的引用,可以直接执行:

bool isSmall = f(2); // isSmall is now true

变量 e 是对表达式树的引用,不可直接执行:

bool isSmall = e(2); // compile error, expressions == data

与委托(有效的不透明代码)不同,我们可以像与程序中的任何其他数据结构交互那样与表达式树进行交互。例如,以下程序:

Expression<FUNC> filter = n => n < 5;

BinaryExpression    body  = (BinaryExpression)filter.Body;
ParameterExpression left  = (ParameterExpression)body.Left;
ConstantExpression  right = (ConstantExpression)body.Right;

Console.WriteLine("{0} {1} {2}",
                  left.Name, body.NodeType, right.Value);

在运行时分解表达式树,并显示以下字符串:

n LT 5

对于启用第三方库(利用属于平台一部分的基本查询抽象)的环境,这种在运行时将表达式视为数据的功能很重要。DLinq 数据访问实现利用该功能将表达式树转换为适用于在存储中计算的 T-SQL 语句。
 

回复:Linq简介

扩展方法

λ 表达式是查询体系结构的一个重要部分。扩展方法 是另一个重要部分。扩展方法将动态语言中常见的“快速输入”的灵活性与静态输入语言的性能和编译时验证结合在一起。通过扩展方法,第三方可以使用新方法增加一个类型的公共协定,同时仍然允许单个类型创作者为这些方法提供他们自己的特定实现。

扩展方法在静态类中定义为静态方法,但在 CLR 元数据中以 [System.Runtime.CompilerServices.Extension] 属性标记。我们鼓励语言为扩展方法提供直接语法。在 C# 中,扩展方法由 this 修饰符指示,该修饰符必须应用于扩展方法的第一个参数。我们来看一下最简单的查询操作符 Where 的定义:

namespace System.Query {
  using System;
  using System.Collections.Generic;

  public static class Sequence {
    public static IEnumerable Where(
            this IEnumerable source,
                  Func predicate) {

      foreach (T item in source)
        if (predicate(item))
          yield return item;
    }
  }
}

扩展方法第一个参数的类型指示该扩展应用于哪种类型。在上述示例中,Where 扩展方法将扩展 IEnumerable 类型。由于 Where 是静态方法,因此我们可以像调用任何其他静态方法那样直接调用它:

IEnumerable expr = Sequence.Where(names,
                                          s => s.Length < 6);

但是,扩展方法的特殊之处在于,它们还可以通过实例语法来调用:

IEnumerable expr = names.Where(s => s.Length < 6);

扩展方法在编译时根据哪些扩展方法在范围内进行解析。当一个命名空间与 C# 的 using 语句或 VB 的 Import 语句一起导入时,由该命名空间的静态类定义的所有扩展方法将导入范围中。

标准查询操作符将定义为 System.Query.Sequence 类型的扩展方法。在检查标准查询操作符时,您将注意到,除了一个以外,所有操作符都可以定义为 IEnumerable 接口(这个例外是 OfType,我们将在后文加以说明)。这意味着,每个与 IEnumerable 兼容的信息源都可以通过在 C# 中添加以下 using 语句来轻松地获得标准查询操作符:

using System.Query; // makes query operators visible

希望将标准查询操作符替换为特定类型的用户可以:(a) 使用兼容的签名在特定类型上定义他们自己的同名方法,或者 (b) 定义可扩展特定类型的新的同名扩展方法。希望完全避免标准查询操作符的用户只能将 System.Query 置于范围以外,并为 IEnumerable 编写他们自己的扩展方法。

对于解析而言,扩展方法具有最低的优先权,并且只有在没有合适的目标类型及其基类型的匹配时才使用。这允许用户定义的类型提供他们自己的、优于标准操作符的查询操作符。例如,请考虑以下自定义集合:

public class MySequence : IEnumerable {
  public IEnumerator GetEnumerator() {
    for (int i = 1; i <= 10; i++)
      yield return i;
  }

  IEnumerator IEnumerable.GetEnumerator() {
    return GetEnumerator();
  }

  public IEnumerable Where(Func filter) {
    for (int i = 1; i <= 10; i++)
      if (filter(i))
        yield return i;
  }
}

假定使用该类定义,以下程序:

MySequence s = new MySequence();
foreach (int item in s.Where(n => n > 3))
    Console.WriteLine(item);

将使用 MySequence.Where 实现,而不是扩展方法,因为实例方法优于扩展方法。

前面提到的 OfType 操作符是一个无法扩展基于 IEnumerable 的信息源的标准操作符。下面,我们来看一下 OfType 查询操作符:

public static IEnumerable OfType(this IEnumerable source) {
  foreach (object item in source)
    if (item is T)
      yield return (T)item;
}

OfType 不仅接受基于 IEnumerable 的源,还接受针对非参数化 IEnumerable 接口(在 .NET Framework 1.0 版本中提供)编写的源。OfType 操作符允许用户将标准查询操作符应用于以下传统的 .NET 集合:

// "classic" cannot be used directly with query operators
IEnumerable classic = new OlderCollectionType();

// "modern" can be used directly with query operators
IEnumerablemodern = classic.OfType();

在本例中,变量 modern 生成了与 classic 相同的值序列,但其类型与现在的 IEnumerable 代码兼容,包括标准查询操作符。

OfType 操作符对于较新的信息源也很有用,因为它允许根据类型从源筛选值。在生成新序列时,OfType 只省略原始序列中与类型参数不兼容的成员。请考虑下面这个简单的程序,它将从异类数组中提取字符串:

object[] vals = { 1, "Hello", true, "World", 9.1 };
IEnumerable justStrings = vals.OfType();

当我们在 foreach 语句中枚举 justStrings 变量时,将获得一个由两个字符串(“Hello”和“World”)组成的序列。
 

回复:Linq简介

延迟的查询计算

观察力敏锐的读者可能会注意到,标准的 Where 操作符是使用 C# 2.0 中引入的 yield 结构实现的。该实现技术常用于返回值序列的所有标准操作符。使用 yield 的一个有趣的优点是,查询实际上是在迭代完毕后计算的(通过使用 foreach 语句,或者手动使用基础的 GetEnumerator 和 MoveNext 方法)。该延迟计算允许将查询保留为基于 IEnumerable 的值,这些值可以计算多次,每次都可能生成不同的值。

对于许多应用程序而言,这正是所需的行为。对于希望缓存查询计算结果的应用程序而言,提供的两个操作符(ToList 和 ToArray)会强制立即计算查询,并返回包含查询计算结果的 List 或数组。

要了解延迟查询计算如何工作,请考虑以下程序,该程序对数组运行了一个简单的查询:

// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };

// declare a variable that represents a query
IEnumerable ayes = names.Where(s => s[0] == 'A');

// evaluate the query
foreach (string item in ayes)
  Console.WriteLine(item);

// modify the original information source
names[0] = "Bob";

// evaluate the query again, this time no "Allen"
foreach (string item in ayes)
    Console.WriteLine(item);

每次迭代变量 ayes 时,都会计算查询。要指示所需结果的缓存副本,我们只需在查询中追加一个 ToList 或 ToArray 操作符,如下所示:

// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };

// declare a variable that represents the result
// of an immediate query evaluation
string[] ayes = names.Where(s => s[0] == 'A').ToArray();

// iterate over the cached query results
foreach (string item in ayes)
    Console.WriteLine(item);

// modifying the original source has no effect on ayes
names[0] = "Bob";

// iterate over result again, which still contains "Allen"
foreach (string item in ayes)
    Console.WriteLine(item);

ToArray 和 ToList 都可以强制立即执行查询计算,这与返回单个值的标准查询操作符(例如,First、ElementAt、Sum、Average、All 和 Any)一样。
 

回复:Linq简介

初始化复合值

λ 表达式和扩展方法为我们提供了只从值序列筛选成员的查询所需的全部内容。大多数查询表达式还针对这些成员执行投影,将原始序列的成员有效地转换为值和类型可能不同于原先的成员。要支持编写这些转换,LINQ 依赖一个名为对象初始化表达式 的新结构,以创建结构化类型的新实例。在本文其余部分中,我们将假设定义了以下类型:

public class Person {
  string name;
  int age;
  bool canCode;

  public string Name {
    get { return name; } set { name = value; }
  }

  public int Age {
    get { return age; } set { age = value; }
  }

  public bool CanCode {
    get { return canCode; } set { canCode = value; }
  }
}

对象初始化表达式使我们能够根据类型的公共字段和属性轻松地生成值。例如,要创建 Person 类型的新值,我们可以编写以下语句:

Person value = new Person {
    Name = "Chris Smith", Age = 31, CanCode = false
};

从语义上说,该语句等效于以下语句序列:

Person value = new Person();
value.Name = "Chris Smith";
value.Age = 31;
value.CanCode = false;

对象初始化表达式是语言集成查询的一个重要功能,因为它们允许在仅允许表达式的上下文(如 λ 表达式和表达式树)中生成新的结构化值。例如,请考虑以下查询表达式,它为输入序列中的每个值创建了新的 Person 值:

IEnumerable expr = names.Select(s => new Person {
    Name = s, Age = 21, CanCode = s.Length == 5
});

对象初始化语法也可以方便地用于初始化结构化值的数组。例如,请考虑以下数组变量,该变量是使用单个的对象初始值设定项来初始化的:

static Person[] people = {
  new Person { Name="Allen Frances", Age=11, CanCode=false },
  new Person { Name="Burke Madison", Age=50, CanCode=true },
  new Person { Name="Connor Morgan", Age=59, CanCode=false },
  new Person { Name="David Charles", Age=33, CanCode=true },
  new Person { Name="Everett Frank", Age=16, CanCode=true },
};
 

回复:Linq简介

结构化值和类型

LINQ 项目支持以数据为中心的编程样式,其中,某些类型的存在主要是为了通过结构化值提供静态“形式”,而不是提供同时具有状态和行为的完整对象。根据它的逻辑结论推测,通常,开发人员所关心的只是值的结构,以及对命名类型的需要,因为该形式很少使用。这就引出了对匿名类型 的介绍,匿名类型允许将新的结构定义为与它们的初始化进行“内联”。

在 C# 中,匿名类型的语法与对象初始化语法完全相同(除了省略了类型的名称)。例如,请考虑以下两个语句:

object v1 = new Person {
    Name = "Chris Smith", Age = 31, CanCode = false
};

object v2 = new { // note the omission of type name
    Name = "Chris Smith", Age = 31, CanCode = false
};

变量 v1 和 v2 都指向一个内存中对象,该对象的 CLR 类型有三个公共属性 — Name、Age 和 CanCode。变量的不同之处在于,v2 引用了匿名类型 的实例。在 CLR 术语中,匿名类型与任何其他类型没有区别。匿名类型的特殊之处在于,它们在编程语言中没有有意义的名称 — 创建匿名类型实例的唯一方法就是使用上述语法。

要使变量能够引用匿名类型的实例,同时仍然从静态类型获益,C# 引入了 var 关键字,以便用于替换局部变量声明的类型名称。例如,请考虑以下合法的 C# 3.0 程序:

var s = "Bob";
var n = 32;
var b = true;

var 关键字会告诉编译器,从用于初始化变量的表达式的静态类型推断出变量的类型。在本例中,s、n 和 b 的类型分别是 string、int 和 bool。该程序与以下程序完全相同:

string s = "Bob";
int    n = 32;
bool  b = true;

var 关键字方便用于其类型名称有意义的变量,但对于引用匿名类型实例的变量而言是必需的。

var value = new {
  Name = "Chris Smith", Age = 31, CanCode = false
};

在上述示例中,变量 value 是匿名类型,其定义与以下伪 C# 等效:

internal class ??? {
  string _Name;
  int    _Age;
  bool  _CanCode;

  public string Name {
    get { return _Name; } set { _Name = value; }
  }

  public int Age{
    get { return _Age; } set { _Age = value; }
  }

  public bool CanCode {
    get { return _CanCode; } set { _CanCode = value; }
  }
}

匿名类型不能跨程序集边界共享;但是,编译器可确保在每个程序集中,属性名/类型对的给定序列最多有一个匿名类型。

由于匿名类型通常用于投影,以选择现有结构化值的一个或多个成员,因此我们只需从匿名类型初始化的另一个值中引用字段或属性。这将导致一个新的匿名类型,其属性的名称、类型和值均从所引用的属性或字段复制而来。

例如,请考虑以下示例,该示例通过组合其他值的属性创建了一个新的结构化值:

var bob = new Person { Name = "Bob", Age = 51, CanCode = true };
var jane = new { Age = 29, FirstName = "Jane" };

var couple = new {
    Husband = new { bob.Name, bob.Age },
    Wife = new { Name = jane.FirstName, jane.Age }
};

int    ha = couple.Husband.Age; // ha == 51
string wn = couple.Wife.Name;  // wn == "Jane"

对上述字段或属性的引用只是一种方便的语法,可用于编写以下更显式的窗体:

var couple = new {
    Husband = new { Name = bob.Name, Age = bob.Age },
    Wife = new { Name = jane.FirstName, Age = jane.Age }
};

在这两个示例中,couple 变量从 bob 和 jane 获得了自己的 Name 和 Age 属性副本。

匿名类型通常用于查询的 select 子句。例如,请考虑以下查询:

var expr = people.Select(p => new {
              p.Name, BadCoder = p.Age == 11
          });

foreach (var item in expr)
  Console.WriteLine("{0} is a {1} coder",
                    item.Name,
                    item.BadCoder ? "bad" : "good");

在本例中,我们能够通过 Person 类型创建新投影,以完全匹配处理代码所需的形式,同时仍然提供静态类型的优势。
 

回复:Linq简介

更多标准查询操作符
除了上述基本查询工具之外,许多操作符也提供了操作序列和编写查询的有用方法,从而在标准查询操作符的方便架构中为用户提供对结果的高级控制。

排序和分组

一般而言,对查询表达式的计算会导致以某种顺序生成一系列值,该顺序是底层信息源的固有顺序。要使开发人员能够显式控制这些值的生成顺序,应定义标准查询操作符来控制该顺序。这些操作符中最基本的就是 OrderBy 操作符。

OrderBy 和 OrderByDescending 操作符可应用于任何信息源,并允许用户提供可生成用于排序结果的值的键值提取函数。OrderBy 和 OrderByDescending 还接受可用于对键施加部分顺序的可选比较函数。下面我们来看一个基本示例:

string[] names = { "Burke", "Connor", "Frank", "Everett",
                  "Albert", "George", "Harris", "David" };

// unity sort
var s1 = names.OrderBy(s => s);
var s2 = names.OrderByDescending(s => s);

// sort by length
var s3 = names.OrderBy(s => s.Length);
var s4 = names.OrderByDescending(s => s.Length);

前两个查询表达式会生成根据字符串比较排序源成员的新序列。后两个查询会生成根据每个字符串的长度排序源成员的新序列。

要允许多个排序准则,OrderBy 和 OrderByDescending 都应该返回 SortedSequence,而不是通用的 IEnumerable。两个操作符仅在 SortedSequence 上定义,分别名为 ThenBy 和 ThenByDescending,它们将应用附加(从属)的排序准则。ThenBy/ThenByDescending 自身会返回 SortedSequence,以允许应用任何数量的 ThenBy/ThenByDescending 操作符:

string[] names = { "Burke", "Connor", "Frank", "Everett",
                  "Albert", "George", "Harris", "David" };

var s1 = names.OrderBy(s => s.Length).ThenBy(s => s);

在本例中,计算由 s1 引用的查询将生成以下值序列:

"Burke", "David", "Frank",
"Albert", "Connor", "George", "Harris",
"Everett"

除了 OrderBy 系列的操作符,标准查询操作符还包括 Reverse 操作符。Reverse 只枚举序列并以相反的顺序生成相同的值。与 OrderBy 不同,Reverse 在决定顺序时不考虑实际值本身,而仅仅依赖于底层源生成的值的顺序。

OrderBy 操作符可对值序列施加排序顺序。标准查询操作符还包括 GroupBy 操作符,该操作符可根据键值提取函数对值序列进行分区。GroupBy 操作符会返回一列 Grouping 值,其中每一个对应于所遇到的不同的键值。每个 Grouping 都包含键,以及映射到该键的值集合。Grouping 的公共协定如下所示:

public sealed class Grouping {
  public Grouping(K key, IEnumerable group);
  public Grouping();
  public K Key { get; set; }
  public IEnumerable Group { set; get; }
}

最简单的 GroupBy 应用程序如下所示:

string[] names = { "Albert", "Burke", "Connor", "David",
                  "Everett", "Frank", "George", "Harris"};

// group by length
var grouping = names.GroupBy(s => s.Length);

foreach (Grouping group in grouping) {
    Console.WriteLine("Strings of length {0}", group.Key);

    foreach (string value in group.Group)
        Console.WriteLine("  {0}", value);
}   

运行后,该程序会显示出以下结果:

Strings of length 6
  Albert
  Connor
  George
  Harris
Strings of length 5
  Burke
  David
  Frank
Strings of length 7
  Everett

Select 和 GroupBy 允许您提供用于填充组成员的投影函数。

string[] names = { "Albert", "Burke", "Connor", "David",
                  "Everett", "Frank", "George", "Harris"};

// group by length
var grouping = names.GroupBy(s => s.Length,
                            s => s[0]);
foreach (Grouping group in grouping) {
    Console.WriteLine("Strings of length {0}", group.Key);

    foreach (char value in group.Group)
        Console.WriteLine("  {0}", value);


该变体会显示以下结果:

Strings of length 6
  A
  C
  G
  H
Strings of length 5
  B
  D
  F
Strings of length 7
  E

从该示例中可以注意到,投影类型不需要与源相同。在本例中,我们从字符串序列创建了整数-字符的分组。
 

回复:Linq简介

聚合操作符

定义几个标准查询操作符,以便将一列值聚合到单个值中。最常见的聚合操作符是 Fold,它定义如下:

public static U Fold(this IEnumerable source,
                          U seed, Func func) {
  U result = seed;

  foreach (T element in source)
      result = func(result, element);

  return result;
}

Fold 操作符使得针对值序列进行计算很简单。Fold 的工作方法是,为底层序列的每个成员调用一次 λ 表达式。每次 Fold 调用 λ 表达式时,都会传递序列的成员和一个聚合值(初始值基于 Fold 的种子参数)。λ 表达式的结果会替换先前的聚合值,Fold 将返回 λ 表达式的最终结果。

例如,以下程序使用 Fold 针对一个字符串数组计算总字符数:

string[] names = { "Albert", "Burke", "Connor", "David",
                  "Everett", "Frank", "George", "Harris"};

int count = names.Fold(0, (c, s) => c + s.Length);
// count == 46

除了通用的 Fold 操作符,标准查询操作符还包含一个通用的 Count 操作符和四个数值聚合操作符(Min、Max、Sum 和 Average),以便简化这些常见的聚合操作。只要提供将序列成员投影到数值类型的函数,数值聚合函数就可以处理数值类型的序列(例如,int、double、decimal)或任意值序列。

以下程序演示 Sum 操作符的上述两种形式:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
string[] names = { "Albert", "Burke", "Connor", "David",
                  "Everett", "Frank", "George", "Harris"};

int total1 = numbers.Sum();            // total1 == 55
int total2 = names.Sum(s => s.Length); // total2 == 46

请注意,第二个 Sum 语句等效于前面使用 Fold 的示例。
 

回复:Linq简介

Select 与 SelectMany

Select 操作符要求转换函数为源序列中的每个值生成一个值。如果转换函数返回的值本身就是一个序列,则应该由使用者手动遍历子序列。例如,请考虑以下程序,该程序使用现有的 String.Split 方法将字符串拆分为标记:

string[] text = { "Albert was here",
                  "Burke slept late",
                  "Connor is happy" };

var tokens = text.Select(s => s.Split(' '));

foreach (string[] line in tokens)
    foreach (string token in line)
        Console.Write("{0}.", token);

运行后,该程序会显示以下文本:

Albert.was.here.Burke.slept.late.Connor.is.happy.

理想情况下,我们应该让查询返回标记的合并序列,并且不对使用者公开中间 string[]。为此,我们将使用 SelectMany 操作符,而不是 Select 操作符。SelectMany 操作符的工作方式类似于 Select 操作符。但不同之处在于,转换函数返回的序列随后由 SelectMany 操作符扩展。下面是使用 SelectMany 重新编写的程序:

string[] text = { "Albert was here",
                  "Burke slept late",
                  "Connor is happy" };

var tokens = text.SelectMany(s => s.Split(' '));

foreach (string token in tokens)
    Console.Write("{0}.", token);

使用 SelectMany 会导致每个中间序列扩展为正常计算的一部分。
 
1  /  2  页   12 跳转

快速回复帖子

标题
禁用 URL 识别
禁用表情
禁用 Discuz!NT 代码
使用个人签名
  [完成后可按 Ctrl+Enter 无刷新发布]  

版权所有 拼吾爱程序人生    

Powered by Discuz!NT 2.1.202   Copyright © 2001-2008 Comsenz Inc. 鄂ICP备07500843号
返顶部