1. 简介 先来看一个简单的例子: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace FirstLINQ { class Program { static void Main() { int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 }; var numQuery = from num in numbers where (num % 2) == 0 select num; foreach (var num in numQuery) Console.Write("{0,1} ", num); Console.ReadLine(); } } } 关键的部分就是from…in…where…select,这个本质上和标准的SQL语句select…from…where… 是一个意思。上面的程序的输出就是: 0 2 4 6. 为了彻底理解LINQ的语法,先介绍下C#语言的新特性。 1. var 关键字 var i = 10; // implicitly typed int i = 10; //explicitly typed 再看一个msdn的例子: // Example #1: var is optional because // the select clause specifies a string string[] words = { "apple", "strwawberry", "grape", "peach", "banana" }; var wordQuery = from word in words where word[0] == 'g' select word; // Because each element in the sequence is a string, // not an anonymous type, var is optional here also. foreach (string s in wordQuery) { Console.WriteLine(s); } // Example #2: var is required because // the select clause specifies an anonymous type var custQuery = from cust in customers where cust.City == "Phoenix" select new { cust.Name, cust.Phone }; // var must be used because each item // in the sequence is an anonymous type foreach (var item in custQuery) { Console.WriteLine("Name={0}, Phone={1}", item.Name, item.Phone); } 2. Auto-Implemented Properties class LightweightCustomer { public LightweightCustomer(double total, string name, int id) { this.Name = name; this.CustomerID = id; this.TotalPurchases = total; } public double TotalPurchases { get; set; } public string Name { get; private set; } // read-only public int CustomerID { get; private set; } // read-only } class Program { static void Main(string[] args) { LightweightCustomer cus = new LightweightCustomer(12.4, "new C#", 1); Console.WriteLine("{0} {1} {2}", cus.CustomerID, cus.Name, cus.TotalPurchases); } } C#编译器会自己建立一个内部的字段。 3 Extension Method 例如: public static class Extensions { public static int ToInt32(this string s) { return Int32.Parse(s); } } 然后就可以这样来调用: string s = "123"; int i = s.ToInt32(); Extension Method 中可以使用泛型。例如: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace UnderstandGenericsAssumption { class Program { static void Main(string[] args) { GenericTypeResolverTest v = new GenericTypeResolverTest(); v.Value = "TEST"; v.Test(); } } //generic implicit type resolver. public class GenericTypeResolverTest { public string Value { get; set; } public override string ToString() { return Value.ToString(); } } public static class GenericTypeResolverMethodTest { public static void Test<T>(this T obj) { Console.WriteLine(obj.ToString()); } } } 一般在使用泛型方法的时候,必须指明类型,例如: static void Swap<T>(ref T lhs, ref T rhs) { T temp; temp = lhs; lhs = rhs; rhs = temp; } public static void TestSwap() { int a = 1; int b = 2; Swap<int>(ref a, ref b); System.Console.WriteLine(a + " " + b); } 但是在Extension Method中,C#可以自动推演泛型类型的真正类型。 4.Lambda 表达式 a => a == "test"; 一个Lambda表达式都可以赋值给一个委托,例如,msdn的例子: delegate int del(int i); del myDelegate = x => x * x; int j = myDelegate(5); //j = 25 LINQ查询语句中的where实际上是一个Extension Method,位于System.Linq.Enumerable 类中,其声明如下: public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate ) 其中 Func是一个委托: public delegate TResult Func<T, TResult>( T arg ) 这个委托表示接受一个类型为T的参数返回类型为Tresult的一类函数,是一个一元运算符,这一类函数特别适合要对序列中每一个对象实行某种操作的情况。Lambda表达式也可以作为参数传给以Func<T,TResult> 为参数类型的函数,例如: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace UnderstandLambda { class Program { static void Main(string[] args) { var lists = new string[] { "code6421", "tom", "david" }; var result = lists.Where(a => a.MyContains("2")); // var result = from s1 in lists where s1.MyContains("code") select s1; foreach (var item in result) Console.WriteLine(item); Console.ReadLine(); } } public static class MyExtensionMethod { public static bool MyContains(this string a,string matchs) { return a.Contains(matchs); } } } 输出的结果是:code6421 Lambda表达式可以带有多个参数,例如: (x, y) => x == y; 或者当类型不能确定的时候,可以明确指定: (int x, string s) => s.Length > x; 也可以没有参数: () => SomeMethod(); 也可以包括多条语句: delegate void TestDelegate(string s); … TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); }; myDel("Hello"); 5. Object and Collection Initializers private class Cat { // Auto-implemented properties public int Age { get; set; } public string Name { get; set; } } static void MethodA() { // Object initializer Cat cat = new Cat { Age = 10, Name = "Sylvester" }; } Collection Initializer与此是类似的,可以在初始化一个集合对象的时候直接指定他的值: List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; List<int> digits2 = new List<int> { 0 + 1, 12 % 3, MakeInt() }; 或者: List<Cat> cats = new List<Cat> { new Cat(){ Name="Sylvester", Age=8 }, new Cat(){ Name="Whiskers", Age=2}, new Cat() { Name="Sasha", Age=14} }; 2 Linq Query Expression 详解 from <alias> in <expression(datasource)> <where-expression> | <group expression> | <join-expression> | <order-by-expression> | <select-expression> Linq Query Expression 实际上都由编译器转化为函数调用,这些调用的函数都是Extension Method,位于 System.Linq.Enumerable 命名空间中。这些函数基本上都是对IEnumerable<T>进行的操作的,也就是说Linq to Objects实际上是对于IEnumerable<T>类型的对象实行查找、筛选等操作。下面的方法可以用于Linq Query Expression中,自然也可以用于通常的IEnumerable<T>对象。 2.1 嵌套Query Expression int[] numbers = { 0, 3, 4, 5, 6, 7, 8, 13, 16 }; var num= from num1 in (from s in numbers where s>4 select s) where num1%2==0 select num1; foreach(var n in num) Console.Write("{0 }",n); 输出结果为 6 8 16。 通常使用var来让编译器决定Query Expression的返回值,实际上,大多数情况下,Query Expression是返回的IEnumerable<T>类型,例如,上面的例子可以改写为: IEnumerable<Int32> num= from num1 in (from s in numbers where s>4 select s) where num1%2==0 select num1; 例外是当使用了First()之类的函数的时候,返回的就是T类型的对象本身,这一类函数同样定义正在System.Linq.Enumerable类中。因此,Query Expression 可以嵌套是很自然的。 2.2 where 子句 2.3 group 子句 public interface IGrouping<TKey, TElement> : IEnumerable<TElement>, IEnumerable 也就是:IEnumerable<IGrouping<TKey, TElement>>的对象,本质上是一个链表的链表。 IGrouping 接口本身实现了 IEnumerable接口,但是它还有一个类型为TKey的键,用来标识用来分组的数据项。他有一个只读的属性,Tkey Key,可以获得这个键。例如: private static void TestGroupByLinq() { Person[] persons = new Person[]{ new Person{Name="code6421",Age=18,Address="Taipen"}, new Person{Name="jeffray",Age=18,Address="USA"}, new Person{Name="catch",Age=12,Address="USA"}, new Person{Name="joe",Age=18,Address="TY"}}; var p = from o in persons group o by o.Address into g select new { Address = g.Key, Persons = g }; foreach (var s in p) { Console.WriteLine("Group : {0}", s.Address); foreach (var s1 in s.Persons) Console.WriteLine(s1.Name); } Console.ReadLine(); } 输出的结果是: Group : Taipen code6421 Group : USA jeffray catch Group : TY Joe 其中的into 子句表示把结果集存到一个变量g中,最后的select 子句可以选择需要的列,new 的语法是使用了匿名类。 2.4 join 子句 private static void TestJoin() { var p1 = new[]{new {Name = "code6421", Address = "Taipen"}, new {Name = "tom", Address = "Taipen"}, new {Name = "jeffray", Address = "NY"}}; var p2 = new[]{new {Name = "code6421", Title = "Manager"}, new {Name = "tom", Title = "Director"}, new {Name = "jeffray", Title = "Programmer"}}; var p3 = from s in p1 join s1 in p2 on s.Name equals s1.Name select new { Name = s.Name, Address = s.Address, Title = s1.Title }; foreach (var v in p3) Console.WriteLine("Name {0}, Address {1}, Title {2}", v.Name, v.Address, v.Title); } 输出的结果是: Name code6421, Address Taipen, Title Manager Name tom, Address Taipen, Title Director Name jeffray, Address NY, Title Programmer 2.5 orderby 子句 { string[] list = new string[] { "1111", "2222", "3333" }; var p = from o in list orderby o descending select o; foreach (var s in p) Console.WriteLine(s); } 输出的结果是:3333 2222 1111 descending表示降序,ascending表示升序。 2.6 Distinct 方法 private static void TestSelectDistinct() { var p1 = new[]{ new {Name = "code6421", Age=18, Address = "Taipen"}, new {Name = "cathy", Age=18, Address = "Taipen"}, new {Name = "tom", Age=18, Address = "NY"}}; var p2 = (from s in p1 select s.Address).Distinct(); foreach (var p4 in p2) Console.WriteLine(p4); } 结果是: Taipen NY Distinct方法也可以接受一个实现IEqualityComparer<T>接口的对象,来实现自定义的对象比较,例如: private static void TestSelectDistinctWithCondition() { var p1 = new Person[]{ new Person(){Name = "code6421", Age=18, Address = "Taipen"}, new Person(){Name = "cathy", Age=18, Address = "Taipen"}, new Person(){Name = "tom", Age=18, Address = "NY"}}; var p2 = (from s in p1 select s).Distinct(new MyObjectComparer<Person>() { PropertyName = "Address" }); foreach (var p4 in p2) Console.WriteLine(p4.Name); } 其中MyObjectComparer类定义如下: public sealed class MyObjectComparer<T> : IEqualityComparer<T> { public string PropertyName { get; set; } #region IEqualityComparer<T> Members public bool Equals(T x, T y) { if (x == null && y == null) return true; if (x == null && y != null || x != null && y == null) return false; PropertyInfo pi1 = x.GetType().GetProperty(PropertyName); return pi1.GetValue(x, null).Equals(pi1.GetValue(y, null)); } public int GetHashCode(T obj) { if (obj == null) return -1; PropertyInfo pi = obj.GetType().GetProperty(PropertyName); object v1 = pi.GetValue(obj, null); if (v1 == null) return -1; return v1.GetHashCode(); } #endregion } 这个类利用反射,实现了根据某个特定的属性来判断连个对象是否相等。 2.7 Take,TakeWhile 方法 private static void TestTake() { var p1 = new[]{ new {Name = "code6421", Age=18, Address = "Taipen"}, new {Name = "cathy", Age=18, Address = "Taipen"}, new {Name = "tom", Age=18, Address = "NY"}}; var p3 = (from s1 in p1 select s1).Take(2); foreach (var p4 in p3) Console.WriteLine(string.Format("Name {0}, Title {1}", p4.Name, p4.Address)); } 运行结果是: Name code6421, Title Taipen Name cathy, Title Taipen TakeWhile方法的声明如下: public static IEnumerable<TSource> TakeWhile<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate ) 使用方法是: var p3 = (from s1 in p1 select s1).TakeWhile(x => x.Address == "Taipen"); 这样输出结果就是: Name code6421, Title Taipen Name cathy, Title Taipen 注意如果筛选条件改为 x => x.Address == "NY",将不输出任何元素,因为列表中第一个元素就不符合条件。 与这两个方法类似的方法还有Skip和SkipWhile方法,他们是跳过相应的元素。 2.8First 和 FirstOrDefault方法 private static void TestFirst() { var p1 = new[]{ new {Name = "code6421", Age=18, Address = "Taipen"}, new {Name = "cathy", Age=18, Address = "Taipen"}, new {Name = "tom", Age=18, Address = "NY"}, new {Name = "tom2", Age=18, Address = "NY"}}; var p3 = (from s1 in p1 select s1).First(); Console.WriteLine(string.Format("Name {0}, Address {1}", p3.Name, p3.Address)); } 运行结果是: Name code6421, Address Taipen 也可以这样: var p3 = (from s1 in p1 select s1).First(x => x.Address == "NY"); 运行结果是: Name tom, Address NY 与这两个对应的方法有 Last和LastOrDefault。 2.9 Any方法 private static void TestAny() { var numbers = new[] { 5, 6, 7, 8 }; var numbers2 = new int[0]; if (numbers.Any()) Console.WriteLine("numbers has element"); if (numbers2.Any()) Console.WriteLine("numbers2 has element"); } 输出的结果是: numbers has element private static void TestAnyWithCondition() { var numbers = new[] { 5, 6, 7, 8 }; if (numbers.Any( x => x > 7)) Console.WriteLine("numbers has some elements greater than 7"); } 输出的结果是: numbers has some elements greater than 7 与这个函数用法类似的方法还有All,Contains 方法,分别用来判断是否所有的元素都满足某一条件和是否包含某一特定的元素。 2.10 Reverse方法 private static void TestReverse() { var numbers = new[] { 8, 9, 10 }; var result = numbers.Reverse(); foreach (var item in result) Console.WriteLine(item); } 输出的结果是:10 9 8 2.11 Concat方法 private static void TestConcat() { var numbers = new[] { 8, 9, 10 }; var numbers2 = new[] { 7, 8, 9, 10, 6 }; var result = numbers.Concat(numbers2); foreach (var item in result) Console.WriteLine(item); } 输出的结果是: 8 9 10 7 8 9 10 6 2.12 ToArray,ToList,ToDictionary方法 ToArray的声明是: public static TSource[] ToArray<TSource>( this IEnumerable<TSource> source ) 例如: private static void TestToArray() { var p1 = new[]{ new {Name = "code6421", Age=18, Address = "Taipen"}, new {Name = "cathy", Age=18, Address = "Taipen"}, new {Name = "tom", Age=18, Address = "NY"}}; var p3 = (from s1 in p1 select s1).ToArray(); foreach (var item in p3) Console.WriteLine(item.Name); } 输出的结果是: code6421 cathy tom ToList方法和这个是类似的,只是返回一个List<T>对象而不是数组。 ToDictionary方法稍稍复杂一些,一共有4个重载方法: public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector ) 这个方法接受一个keySelector参数,这个参数的作用是从一个元素中抽取出作为键的字段,例如: private static void TestToDictionary() { var p1 = new[]{ new {Name = "code6421", Age=18, Address = "Taipen"}, new {Name = "cathy", Age=18, Address = "Taipen"}, new {Name = "tom", Age=18, Address = "NY"}}; var p3 = (from s1 in p1 select s1).ToDictionary(x => x.Name); foreach (var p4 in p3) { Console.WriteLine("Key {0}", p4.Key); Console.WriteLine("Name {0}", p4.Value.Address); } } 运行结果是: Key code6421 Name Taipen Key cathy Name Taipen Key tom Name NY 如果集合中有相同的键值,则会抛出异常 System.ArgumentException: An item with the same key has already been added. public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer ) 这个方法和上一个是一样的,只是可以自定义键判等的方式。 public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector ) 这个方法不仅可以指定哪个字段作为键,还可以知道哪个字段作为值,例如: var p3 = (from s1 in p1 select s1).ToDictionary(x => x.Name,x=>x.Age); foreach (var p4 in p3) { Console.WriteLine("Key {0}", p4.Key); Console.WriteLine("Value {0}", p4.Value); } 运行结果是: Key code6421 Value 18 Key cathy Value 18 Key tom Value 18 public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer ) 同样地,可以指定键判等的方式。 2.14 ToLookup 方法 public class Lookup<TKey, TElement> : ILookup<TKey, TElement>, IEnumerable<IGrouping<TKey, TElement>>, IEnumerable ILookup接口的声明如下: public interface ILookup<TKey, TElement> : IEnumerable<IGrouping<TKey, TElement>>, IEnumerable ILookup接口表示的是一个可重复的词典,也就是每一个键可以映射到多个值。Lookup类实现了ILookup接口,实际上就是一个IGrouping的集合:IEnumerable<IGrouping<TKey,TElement>>. ToLookup也是一种对数据分组的方法。下面是一个使用Lookup的例子: class Package { public string Company; public double Weight; public long TrackingNumber; } public static void LookupExample() { // Create a list of Packages to put into a Lookup data structure. List<Package> packages = new List<Package> { new Package { Company = "Coho Vineyard", Weight = 25.2, TrackingNumber = 89453312L }, new Package { Company = "Lucerne Publishing", Weight = 18.7, TrackingNumber = 89112755L }, new Package { Company = "Wingtip Toys", Weight = 6.0, TrackingNumber = 299456122L }, new Package { Company = "Contoso Pharmaceuticals", Weight = 9.3, TrackingNumber = 670053128L }, new Package { Company = "Wide World Importers", Weight = 33.8, TrackingNumber = 4665518773L } }; // Create a Lookup to organize the packages. Use the first character of Company as the key value. // Select Company appended to TrackingNumber for each element value in the Lookup. Lookup<char, string> lookup = (Lookup<char, string>)packages.ToLookup(p => Convert.ToChar(p.Company.Substring(0, 1)), p => p.Company + " " + p.TrackingNumber); // Iterate through each IGrouping in the Lookup and output the contents. foreach (IGrouping<char, string> packageGroup in lookup) { // Print the key value of the IGrouping. Console.WriteLine(packageGroup.Key); // Iterate through each value in the IGrouping and print its value. foreach (string str in packageGroup) Console.WriteLine(" {0}", str); } // This code produces the following output: // C // Coho Vineyard 89453312 // Contoso Pharmaceuticals 670053128 // L // Lucerne Publishing 89112755 // W // Wingtip Toys 299456122 // Wide World Importers 4665518773 // Get the number of key-collection pairs in the Lookup. int count = lookup.Count; // Select a collection of Packages by indexing directly into the Lookup. IEnumerable<string> cgroup = lookup['C']; // Output the results. Console.WriteLine("/nPackages that have a key of 'C':"); foreach (string str in cgroup) Console.WriteLine(str); // This code produces the following output: // Packages that have a key of 'C' // Coho Vineyard 89453312 // Contoso Pharmaceuticals 670053128 // Determine if there is a key with the value 'G' in the Lookup. bool hasG = lookup.Contains('G'); } 2.15 Union 、Intersect 和 Except 方法 private static void TestUnion() { var p1 = new int[] { 1, 3, 5, 7, 9, 11 }; var p2 = new int[] { 2, 4, 6, 8, 10, 11 }; var p3 = from s in p1 select s; var p4 = from s in p2 select s; var p5 = p3.Union(p4); var p6 = p3.Intersect(p4); foreach (var i in p5) Console.Write("{0} ", i); Console.WriteLine(); foreach (var i in p6) Console.Write("{0} ", i); } 运行结果如下: 1 3 5 7 9 11 2 4 6 8 10 11 2.16 Sum,Average,Min,Max,Count private static void TestSum1() { var p1 = new[] { 18, 20, 25 }; var p3 = p1.Sum(); Console.WriteLine(p3); } 运行结果是: 63 2.17 Aggerate方法 public static TSource Aggregate<TSource>( this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func ) 这里的Func的定义如下: public delegate TResult Func<T1, T2, TResult>( T1 arg1, T2 arg2 ) 表示一个二元运算符。 例如: private static void TestAggregate() { var p1 = new[] { 18, 20, 25 }; var p3 = p1.Aggregate((x, y) => x * y); Console.WriteLine(p3); } 输出结果是: 9000. 也就是 18×20×25=9000. 2.18 let 子句 private static void UseLet() { string[] list = new string[] { "Code6421 Huang", "Tom Do", "Cathy Chang" }; var result = from s1 in list let words = s1.Split(' ') from word in words let w = word.ToLower() where w[0] == 'c' select word; foreach (var item in result) Console.WriteLine(item); } 运行结果是: Code6421 Cathy Chang 3 LINQ to XML 3.1 创建XML文档 static void CreateXml() { XDocument doc = new XDocument(new XElement("Customers", new XElement("Customer", new XAttribute("ID", "A0001"), new XAttribute("Name", "Tom"), new XAttribute("Address", "Taipen")), new XElement("Customer", new XAttribute("ID", "A0002"), new XAttribute("Name", "Mary"), new XAttribute("Address", "LD")), new XElement("Customer", new XAttribute("ID", "A0003"), new XAttribute("Name", "Jeff"), new XAttribute("Address", "YW")))); doc.Save("customers.xml"); } 这样就生成了一个customers.xml文件,文件内容如下: <?xml version="1.0" encoding="utf-8"?> <Customers> <Customer ID="A0001" Name="Tom" Address="Taipen" /> <Customer ID="A0002" Name="Mary" Address="LD" /> <Customer ID="A0003" Name="Jeff" Address="YW" /> </Customers> 创建的过程是很明了的,System.Xml.Linq命名空间中有很多类,可以和XML中的概念对应起来,例如:XML文档用XDocument类表示,XML文档中的元素用XElement类表示,元素的属性用XAttribute类表示,Xtext表示一个text结点等等。具体可以参考msdn中System.Xml.Linq的文档。 3.2 读取XML文档 static void ReadXml() { XDocument doc = XDocument.Load("customers.xml"); foreach (XElement elem in doc.Elements("Customers").Descendants("Customer")) Console.WriteLine(string.Format("Customer ID : {0}, Name : {1}, Address : {2}", elem.Attribute("ID").Value, elem.Attribute("Name").Value, elem.Attribute("Address").Value)); Console.ReadLine(); } |
LINQ 介绍
最新推荐文章于 2025-07-23 10:12:56 发布
2009-10-21 17:27