LINQ 入门






4.67/5 (13投票s)
一篇阐述 LINQ 背后一些基本概念的文章。
引言
本文的目的是为可能尚未理解 LINQ 的某些个人介绍一些关于 LINQ 的基本知识。LINQ 统一了数据访问,无论数据源是什么,并允许混合来自不同来源的数据。 LINQ 意为“语言集成查询”。它允许进行查询和集合操作,类似于 SQL 语句为数据库提供的功能。然而,LINQ 通过对 C# 和 Visual Basic 等 .NET 语言的一组扩展,将查询直接集成到这些语言中。
在 LINQ 出现之前,开发人员在使用 C# 或 VB.NET 等通用语言编写的每个应用程序中,都必须处理不同的语言,如 SQL、XML 或 XPath,以及各种技术和 API,如 ADO.NET 或 System.Xml。毫无疑问,这存在几个缺点。 LINQ 将几个世界焊接在一起。它帮助我们避免了在从一个世界到另一个世界的道路上通常会遇到的障碍:使用 XML 与对象、混合关系数据与 XML,这些都是 LINQ 将简化的一些任务。 LINQ 的一个关键方面是,它被设计为用于任何类型的对象或数据源,并为此提供一致的编程模型。语法和概念在其所有用途中都是相同的:一旦您学会了如何使用 LINQ 处理数组或集合,您也就知道了利用 LINQ 处理数据库或 XML 文件所需的大部分概念。 LINQ 的另一个重要方面是,当您使用它时,您工作在一个强类型世界中。检查这段基本代码,看看它是否显示与数据源的任何链接
using System;
using System.Linq;
public sealed class Program {
static double Square(double n)
{
Console.WriteLine("Computing Square("+n+")...");
return Math.Pow(n, 2);
}
public static void Main()
{
int[] numbers = {1, 2, 3};
var query =
from n in numbers
select Square(n);
foreach (var n in query)
Console.WriteLine(n);
}
}
Computing Square(1)...
1
Computing Square(2)...
4
Computing Square(3)...
9
该代码声明了一个方法 Square
,然后声明一个隐式类型的局部变量来对一个由三个整数组成的数组(或序列)执行该操作。 select
方法发出一个序列,其中每个输入元素都在给定的 lambda 表达式中转换。每个元素的迭代使操作能够在每个元素上执行。事实上,枚举器的基本思想是一种其唯一目的是遍历和读取另一个集合内容的类型。枚举器不提供写入功能。这种类型可以被视为一个光标,它一次一个地遍历集合中的每个单独元素。 IEnumerable<t>
表示其内容可以被枚举的类型,而 IEnumerator<t>
是负责执行实际枚举的类型。 LINQ 中的基本数据单位是序列和元素。序列是任何实现泛型 IEnumerable
接口的对象,而元素是序列中的每个项目。这是一个基本代码示例
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
string[] names = { "Tom", "Mitch", "Steve" };
IEnumerable <string> filteredNames =
System.Linq.Enumerable.Where (names, n => n.Length >= 4);
foreach ( string n in filteredNames)
sConsole.Write(n + "|");
}
}
这是输出
Mitch
Steve
Lambda 表达式:链接查询运算符
前面的示例不太现实,因为它展示了两个基本的 lambda 查询,每个查询都包含一个查询运算符。要构建更复杂的查询,您需要链接运算符
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
string[] names = { "Tom", "Dick", "Harry",
"Mary", "Jay" };
IEnumerable<string> query = names
.Where (n => n.Contains ("a"))
.OrderBy (n => n.Length)
.Select (n => n.ToUpper());
foreach (string name in query)
Console.Write(name + "|");
}
}
// end of program
// The same query constructed progressively:
IEnumerable<string> filtered = names.Where (n => n.Contains ("a"));
IEnumerable<string> sorted = filtered.OrderBy (n => n.Length);
IEnumerable<string> finalQuery = sorted.Select (n => n.ToUpper());
这是一个更复杂的查询,它通过使用关键字 "var
" 来使用隐式类型的局部变量
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
static class LanguageFeatures
{
class ProcessData
{
public Int32 Id { get; set; }
public Int64 Memory { get; set; }
public String Name { get; set; }
}
static void DisplayProcesses(Func<Process, Boolean> match)
{
// implicitly-typed local variables
var processes = new List<ProcessData>();
foreach (var process in Process.GetProcesses())
{
if (match(process))
{
// object initializers
processes.Add(new ProcessData
{
Id = process.Id,
Name = process.ProcessName,
Memory = process.WorkingSet64
});
}
}
// extension methods
Console.WriteLine("Total memory: {0} MB",
processes.TotalMemory() / 1024 / 1024);
var top2Memory =
processes
.OrderByDescending(process => process.Memory)
.Take(2)
.Sum(process => process.Memory) / 1024 / 1024;
Console.WriteLine(
"Memory consumed by the two most hungry processes: {0} MB",
top2Memory);
// anonymous types
var results = new
{
TotalMemory = processes.TotalMemory() / 1024 / 1024,
Top2Memory = top2Memory,
Processes = processes
};
ObjectDumper.Write(results, 1);
ObjectDumper.Write(processes);
}
static Int64 TotalMemory(this IEnumerable<ProcessData> processes)
{
Int64 result = 0;
foreach (var process in processes)
result += process.Memory;
return result;
}
static void Main()
{
// lambda expressions
DisplayProcesses(process => process.WorkingSet64 >= 20 * 1024 * 1024);
}
}
如果您检查此代码,您将看到 "ObjectDumper
" 未定义,但被引用。这意味着我们有一个 DLL 引用文件需要编译
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
public class ObjectDumper {
public static void Write(object element)
{
Write(element, 0);
}
public static void Write(object element, int depth)
{
Write(element, depth, Console.Out);
}
public static void Write(object element, int depth, TextWriter log)
{
ObjectDumper dumper = new ObjectDumper(depth);
dumper.writer = log;
dumper.WriteObject(null, element);
}
TextWriter writer;
int pos;
int level;
int depth;
private ObjectDumper(int depth)
{
this.depth = depth;
}
private void Write(string s)
{
if (s != null) {
writer.Write(s);
pos += s.Length;
}
}
private void WriteIndent()
{
for (int i = 0; i < level; i++) writer.Write(" ");
}
private void WriteLine()
{
writer.WriteLine();
pos = 0;
}
private void WriteTab()
{
Write(" ");
while (pos % 8 != 0) Write(" ");
}
private void WriteObject(string prefix, object element)
{
if (element == null || element is ValueType || element is string) {
WriteIndent();
Write(prefix);
WriteValue(element);
WriteLine();
}
else {
IEnumerable enumerableElement = element as IEnumerable;
if (enumerableElement != null) {
foreach (object item in enumerableElement) {
if (item is IEnumerable && !(item is string)) {
WriteIndent();
Write(prefix);
Write("...");
WriteLine();
if (level < depth) {
level++;
WriteObject(prefix, item);
level--;
}
}
else {
WriteObject(prefix, item);
}
}
}
else {
MemberInfo[] members = element.GetType().GetMembers(
BindingFlags.Public | BindingFlags.Instance);
WriteIndent();
Write(prefix);
bool propWritten = false;
foreach (MemberInfo m in members) {
FieldInfo f = m as FieldInfo;
PropertyInfo p = m as PropertyInfo;
if (f != null || p != null) {
if (propWritten) {
WriteTab();
}
else {
propWritten = true;
}
Write(m.Name);
Write("=");
Type t = f != null ? f.FieldType : p.PropertyType;
if (t.IsValueType || t == typeof(string)) {
WriteValue(f != null ? f.GetValue(element) :
p.GetValue(element, null));
}
else {
if (typeof(IEnumerable).IsAssignableFrom(t)) {
Write("...");
}
else {
Write("{ }");
}
}
}
}
if (propWritten) WriteLine();
if (level < depth) {
foreach (MemberInfo m in members) {
FieldInfo f = m as FieldInfo;
PropertyInfo p = m as PropertyInfo;
if (f != null || p != null) {
Type t = f != null ? f.FieldType : p.PropertyType;
if (!(t.IsValueType || t == typeof(string))) {
object value = f != null ?
f.GetValue(element) : p.GetValue(element, null);
if (value != null) {
level++;
WriteObject(m.Name + ": ", value);
level--;
}
}
}
}
}
}
}
}
private void WriteValue(object o)
{
if (o == null) {
Write("null");
}
else if (o is DateTime) {
Write(((DateTime)o).ToShortDateString());
}
else if (o is ValueType || o is string) {
Write(o.ToString());
}
else if (o is IEnumerable) {
Write("...");
}
else {
Write("{ }");
}
}
}
现在,我们使用命令行上的 /target:library 选项将 ObjectDumper.cs 文件编译成 DLL,或者在 VS 2010 中将其编译为类文件。请注意,如果您使用的是 VS 2010,请确保转到项目的属性并确保 .NET 平台是 4.0。现在,我们使用对 ObjectDumper.dll 的引用来编译上面的文件 MyProgram.cs:csc.exe /r:ObjectDumper.dll MyProgram.cs。这是输出
C:\Windows\MICROS~1.NET\FRAMEW~1\V40~1.303>myprogram
Total memory: 968 MB
Memory consumed by the two most hungry processes: 314 MB
TotalMemory=968 Top2Memory=314 Processes=...
Processes: Id=3244 Memory=65527808 Name=sqlservr
Processes: Id=5320 Memory=23556096 Name=sqlservr
Processes: Id=3320 Memory=37498880 Name=DkService
Processes: Id=952 Memory=47443968 Name=svchost
Processes: Id=5272 Memory=167903232 Name=WINWORD
Processes: Id=1108 Memory=68866048 Name=svchost
Processes: Id=1096 Memory=90230784 Name=svchost
Processes: Id=500 Memory=120848384 Name=AcroRd32
Processes: Id=2856 Memory=75415552 Name=explorer
Processes: Id=1672 Memory=71299072 Name=digitaleditions
Processes: Id=4348 Memory=162045952 Name=LINQPad
Processes: Id=2576 Memory=35442688 Name=Babylon
Processes: Id=2172 Memory=49131520 Name=SearchIndexer
Id=3244 Memory=65527808 Name=sqlservr
Id=5320 Memory=23556096 Name=sqlservr
Id=3320 Memory=37498880 Name=DkService
Id=952 Memory=47443968 Name=svchost
Id=5272 Memory=167903232 Name=WINWORD
Id=1108 Memory=68866048 Name=svchost
Id=1096 Memory=90230784 Name=svchost
Id=500 Memory=120848384 Name=AcroRd32
Id=2856 Memory=75415552 Name=explorer
Id=1672 Memory=71299072 Name=digitaleditions
Id=4348 Memory=162045952 Name=LINQPad
Id=2576 Memory=35442688 Name=Babylon
Id=2172 Memory=49131520 Name=SearchIndexer