LINQ 初学
语言集成查询 (LINQ) 是 Visual Studio 2008 中的一组功能,可为 C# 和 Visual Basic 语言语法提供强大的查询功能。为以下各种数据源编写 LINQ 查询:SQL Server 数据库、XML 文档、ADO.NET 数据集以及支持 IEnumerable 或泛型 IEnumerable<T> 接口的任意对象集合。
所有 LINQ 查询操作都由以下三个不同的操作组成:
- 获取数据源。
- 创建查询。
- 执行查询。
Example:
Int32[] numbers = new Int32[7] { 0, 1, 2, 3, 4, 5, 6 }; var numQuery = from num in numbers where (num % 2) == 0 select num; foreach (Int32 num in numQuery) { Console.WriteLine(num); }
注: 数据源是数组,因此它隐式支持泛型 IEnumerable接口。
var 关键字指示编译器根据初始化语句右侧的表达式推断变量的类型。var 关键字并不意味着“变体”,也不表示该变量是松散类型化变量或后期绑定变量。它只是表示由编译器确定和分配最适当的类型。
匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。类型名由编译器生成,并且不能在源代码级使用。这些属性的类型由编译器推断。
Example:
var v = new { Amount = 108, Message = "Hello" };
Example:
Int32[] numbersA = { 0, 2, 4, 5, 6, 8, 9 }; Int32 [] numbersB = { 1, 3, 5, 7, 8 }; var pairs = from a in numbersA from b in numbersB where a < b select new { a, b }; Console.WriteLine("Pairs where a < b:"); foreach (var pair in pairs) { Console.WriteLine("{0} is less than {1}", pair.a, pair.b); }
该查询表达式包含三个子句:from、where 和 select,from 子句指定数据源,where 子句应用筛选器,select 子句指定返回的元素的类型。
在LINQ 中,查询变量本身不执行任何操作并且不返回任何数据。它只是存储在以后某个时刻执行查询时为生成结果而必需的信息。查询变量本身只是存储查询命令。实际的查询执行会延迟到在 foreach 语句中循环访问查询变量时发生。此概念称为“延迟执行”。
Example:
Int32[] numbers = new Int32[7] { 0, 1, 2, 3, 4, 5, 6 }; List<Int32> list = new List<Int32>(numbers); var numQuery = from num in list where (num % 2) == 0 select num; foreach (Int32 num in numQuery) { Console.Write("{0,5}",num); } // 0 2 4 6 Console.WriteLine(); list.Remove(2); list.Remove(4); foreach (Int32 num in numQuery) { Console.Write("{0,5}",num); } // 0 6 Console.WriteLine();
若要强制立即执行任意查询并缓存其结果,可以调用 ToList<TSource> 或 ToArray<TSource> 方法。
Example:
List<int> numQuery2 = (from num in numbers where (num % 2) == 0 select num).ToList();
Linq查询关键字
子句 说明
from 指定数据源和范围变量(类似于迭代变量)。
where 根据一个或多个由逻辑“与”和逻辑“或”运算符(&& 或 ||)分隔的布尔表达式筛选源元素。
select 指定当执行查询时返回的序列中的元素将具有的类型和形式。
group 按照指定的键值对查询结果进行分组。
into 提供一个标识符,它可以充当对 join、group 或 select 子句的结果的引用。
orderby 基于元素类型的默认比较器按升序或降序对查询结果进行排序。
join 基于两个指定匹配条件之间的相等比较来联接两个数据源。
let 引入一个用于存储查询表达式中的子表达式结果的范围变量。
in join 子句中的上下文关键字。
on join 子句中的上下文关键字。
equals join 子句中的上下文关键字。
by group子句中的上下文关键字。
ascending orderby子句中的上下文关键字。
descending orderby子句中的上下文关键字。
获取数据源
在 LINQ 查询中,第一步是指定数据源。像在大多数编程语言中一样,在 C# 中,必须先声明变量,才能使用它。在 LINQ 查询中,最先使用 from 子句的目的是引入数据源 (customers) 和范围变量 (cust)。
var queryAllCustomers = from cust in customers select cust;
对于非泛型数据源(如 ArrayList),必须显式类型化范围变量。使用Enumerable.OfType<TResult> 方法,根据指定类型筛选 IEnumerable 的元素。
Example:
ArrayList fruits = new ArrayList(4); fruits.Add("Mango"); fruits.Add("Orange"); fruits.Add("Apple"); fruits.Add(3.0); fruits.Add("Banana"); // Apply OfType() to the ArrayList. IEnumerable<string> query1 = fruits.OfType<string>();
筛选
最常用的查询操作是应用布尔表达式形式的筛选器。此筛选器使查询只返回那些表达式结果为 true 的元素。使用 where 子句生成结果。实际上,筛选器指定从源序列中排除哪些元素。
var queryLondonCustomers = from cust in customers where cust.City == "London" select cust;
排序
通常可以很方便地将返回的数据进行排序。orderby 子句将使返回的序列中的元素按照被排序的类型的默认比较器进行排序。
var queryLondonCustomers3 = from cust in customers where cust.City == "London" orderby cust.Name ascending select cust;
分组
用 group 子句,您可以按指定的键分组结果。例如,您可以指定结果应按 City 分组,以便位于伦敦或巴黎的所有客户位于各自组中。在本例中,cust.City 是键。
// queryCustomersByCity is an IEnumerable<IGrouping<string, Customer>> var queryCustomersByCity = from cust in customers group cust by cust.City; // customerGroup is an IGrouping<string, Customer> foreach (var customerGroup in queryCustomersByCity) { Console.WriteLine(customerGroup.Key); foreach (Customer customer in customerGroup) { Console.WriteLine(" {0}", customer.Name); } }
---------------------------------------------------------------------------------
支持 LINQ 的 C# 3.0 功能
隐式类型化变量 (var)
不必在声明并初始化变量时显式指定类型,您可以使用 var 修饰符来指示编译器推断并分配类型。
Example:
var number = 5;
声明为 var 的变量与显式指定其类型的变量一样都是强类型。通过使用 var,可以创建匿名类型,但它可用于任何局部变量。也可以使用隐式类型声明数组。
对象和集合初始值设定项
通过对象和集合初始值设定项,初始化对象时无需为对象显式调用构造函数。
Example:
Customer cust = new Customer {Name = "Mike" ; Phone ={ "555-1212 "}; 查询表达式经常使用匿名类型,而这些类型只能使用对象初始值设定项进行初始化。
Example:
var productInfos = from p in products select new { p.ProductName, p.UnitPrice };
注:将集合初始值设定项用于可为 null 的结构会导致编译时错误。
匿名类型
匿名类型由编译器构建,且类型名称只可用于编译器。匿名类型提供了一种在查询结果中临时分组一组属性的方便方法,无需定义单独的命名类型。
Int32[] numbersA = { 0, 2, 4, 5, 6, 8, 9 }; Int32[] numbersB = { 1, 3, 5, 7, 8 }; var pairs = from a in numbersA from b in numbersB where a < b select new { Num1 = a , Num2 = b }; Console.WriteLine("Pairs where a < b:"); foreach (var pair in pairs) { Console.WriteLine("{0} is less than {1}", pair.Num1, pair.Num2); }
如果您没有在匿名类型中指定成员名称,编译器会为匿名类型成员指定与用于初始化这些成员的属性相同的名称。必须为使用表达式初始化的属性提供名称
var productQuery = from prod in products select new { prod.Color, prod.Price }; foreach (var v in productQuery) { Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price); }
匿名类型是直接从对象派生的引用类型。尽管应用程序无法访问匿名类型,但编译器仍会为其提供一个名称。从公共语言运行时的角度来看,匿名类型与任何其他引用类型没有什么不同,唯一区别在于匿名类型无法强制转换为除 object 以外的任何类型。
扩展方法
扩展方法是一种可与类型关联的静态方法,因此可以像实例方法那样对类型调用它。实际上,此功能使您能够将新方法“添加”到现有类型,而不会实际修改它们。标准查询运算符是一组扩展方法,它们为实现 IEnumerable的任何类型提供 LINQ 查询功能。
定义和调用扩展方法
1. 定义一个静态类以包含扩展方法。
2. 将该扩展方法实现为静态方法,并使其至少具有与包含类相同的可见性。
3. 该方法的第一个参数指定方法所操作的类型;该参数必须以 this 修饰符开头。
4. 在调用代码中,添加一条 using 指令以指定包含扩展方法类的命名空间。
5. 按照与调用类型上的实例方法一样的方式调用扩展方法。
请注意,第一个参数不是由调用代码指定的,因为它表示正应用运算符的类型,并且编译器已经知道对象的类型。您只需通过 n 为这两个形参提供实参。
Example:
public static class IntExtension { public static Int32 Reverse(this Int32 i) { Char[] temp = i.ToString().ToCharArray(); Array.Reverse(temp); String res = new String(temp); return Convert.ToInt32(res); } } class Program { static void Main(string[] args) { Int32 i = 12345; Console.WriteLine(i.Reverse()); } }
自动实现的属性
通过自动实现的属性,可以更简明地声明属性。当您如下面的示例中所示声明属性时,编译器将创建一个私有的匿名支持字段,该字段只能通过属性 getter 和 setter 进行访问。 public string Name {get; set;} 自动实现的属性必须同时声明 get 和 set 访问器。若要创建 readonly 自动实现属性,请给予它 private set 访问器。 public string Name { get; private set; } // read-only
Lambda
Lambda 表达式
Lambda 表达式是一种内联函数,该函数使用 => 运算符将输入参数与函数体分离,并且可以在编译时转换为委托或表达式树。在 LINQ 编程中,在对标准查询运算符进行直接方法调用时,会遇到 lambda 表达式。
“Lambda 表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式目录树类型。
所有 Lambda 表达式都使用 Lambda 运算符 =>,该运算符读为“goes to”。该 Lambda 运算符的左边是输入参数(如果有),右边包含表达式或语句块。
=> 运算符具有与赋值运算符 (=) 相同的优先级,并且是右结合运算符。
Lambda 表达式返回表达式的结果,并采用以下基本形式:
(input parameters) => expression
只有在 Lambda 有一个输入参数时,括号才是可选的;否则括号是必需的。两个或更多输入参数由括在括号中的逗号分隔:
(x, y) => x == y
有时,编译器难于或无法推断输入类型。如果出现这种情况,您可以按以下示例中所示方式显式指定类型:
(int x, string s) => s.Length > x
使用空括号指定零个输入参数:
() => SomeMethod()
Int32[] numbers = new Int32[7] { 0, 1, 2, 3, 4, 5, 6 }; List<Int32> list = new List<Int32>(numbers); List<Int32> res = list.FindAll(i => i % 2 == 0); foreach (Int32 i in res) { Console.WriteLine(i); }
Lambda 语句
Lambda 语句与 Lambda 表达式类似,只是语句括在大括号中: (input parameters) => {statement;}
Example:
public delegate String StringDel(String str1,String str2); static void Main(string[] args) { StringDel strDel = (s1, s2) => { return s1 + " " +s2; }; Console.WriteLine(strDel("123","456")); //123 456 }