LinQ家族五大成员:
LinQ to Objects – 默认功能,用来实现对内存中集合对象的查询
LinQ to SQL – 针对SQL Server的查询,它是一个带有可视化的操作界面的ORM工具
LinQ to DataSet – 对强类型化或弱类型化的DataSet或独立的DataTable进行查询
LinQ to Entity – 对实体框架中EDM定义的实体集合进行查询。
LinQ to XML – 对XML文档进行查询创建等操作。
C#语法与LinQ相关的新增功能
1.隐式强类型变量
在C#3.0中可以使用var关键字隐式定义强类型局部变量。
《图1》
这里的var关键字定义变量与JavaScript定义变量看起来很像但二者有着本质的区别。
JavaScript定义的变量是弱类型的变量,也可理解为是一种通用类型的变量,它可以容纳各种类型的值,还可以在运行过程中动态修改其中的内容类型。下面这种写法在JavaScript中是正确的:
var obj = 3.14;
obj = "hello world";
C#中的var则是种强类型的变量,它在定义的时候会确定数据类型,分配内存空间。上面这两名代码在C#3.0中会报错,因为第一句已经把obj定义为double型的变量,第二句把字符串赋值给double是错误的操作。
隐式强类型并不能有效简化我的书写的代码,但当我们在用它来动态接收一些未知类型的数据的时候就显虽得很强大。在隐式强类型变量出现前,我们一般是使用object型变量来接收这种未知类型的数据的。
2.对象初始化
这个功能可以有效简化类的getter和setter部份的代码,还可以只使用一行表达式语句实现对象的实例化与初始化操作。
如定义实例类时可以使用如下代码,没有必要再把成员变量和属性分别定义了。
public class LineItem
{
public int OrderID { get; set; }
public int ProductID { get; set; }
public short Quantity { get; set; }
public string QuantityPerUnit { get; set; }
public decimal UnitPrice { get; set; }
public float Discount { get; set; }
}
实例化LineItem对象,并为它赋值
var line3 = new LineItem { OrderID = 11000, ProductID = 61, Quantity = 30, QuantityPerUnit = “12 1-kg cartons”, UnitPrice = 15.55M, Discount = 0.15F };
3.数组初始化
var LineItems = new[]
{
new LineItem {OrderID = 11000, ProductID = 11, Quantity = 10,
QuantityPerUnit = “24 500-g bottles”, UnitPrice = 15.55M, Discount = 0.0F},
new LineItem {OrderID = 11000, ProductID = 21, Quantity = 20,
QuantityPerUnit = “12 1-kg cartons”, UnitPrice = 20.2M, Discount = 0.1F},
new LineItem {OrderID = 11000, ProductID = 31, Quantity = 30,
QuantityPerUnit = “24 1-kg bags”, UnitPrice = 25.45M, Discount = 0.15F}
};
4.集合初始化
var LineItemsList = new List < LineItem >
{
new LineItem {OrderID = 11000, ProductID = 11, Quantity = 10,
QuantityPerUnit = “24 500-g bottles”, UnitPrice = 15.55M, Discount = 0.0F},
new LineItem {OrderID = 11000, ProductID = 21, Quantity = 20,
QuantityPerUnit = “12 1-kg cartons”, UnitPrice = 20.2M, Discount = 0.1F},
new LineItem {OrderID = 11000, ProductID = 31, Quantity = 30,
QuantityPerUnit = “24 1-kg bags”, UnitPrice = 25.45M, Discount = 0.15F}
};
5.匿名类型
过去我们要生成对象时,必须事先定义该对象的类,然后使用new关键字来实例化该类。匿名类型简化定义类的这个过程,我们可以使用new关键字直接把类的定义,类的实例化放在一个表达式语句中。
如:
var obj = new
{
Name = "zhangsan",
Age = 18,
URL = "http://hi.baidu.com/grayworm"
};
Console.WriteLine(obj.Name + obj.Age + obj.URL);
匿名类型主要用在LinQ to SQL中对字段的投影功能上:
var query = from i in LineItems select new { i.OrderID, i.ProductID, i.UnitPrice }
6.扩展方法
扩展方法就是为现有的类追加我们自定义的方法。在C#3.0的集成开发环境中,我们会发现带有向下箭头的方法,这些方法是我们在C#2.0中所没有见到的方法,这些方法就是我们所谓的“扩展方法”,它是C#3.0在C#2.0的基础上新增的一系列的方法,当然我们也可以为内置类添加我们自己的主扩展方法。
《图2》
例如在string中有个Length()扩展方法,它用来取得字符串的长度,但当字符串是null的时候调用该字符串的Length()时候会抛出异常。下面我们为String类添加一个自定义的方法LengthNullable(),如果字符串为null不抛出异常,而返回-1:
代码如下:
static class ExtensionMethods
{
public static int LengthNullable(this string test)
{
if (test != null)
{
return test.Length;
}
else
{
return -1
}
}
}
C#3.0的扩展方法需要单独写在一个public static的类中,并且扩展方法也应当用public static修饰。扩展方法的参数有三部份组成(this string test),第一部份是this关键字,它用来告诉编译器该方法是扩展方法;第二部份是该方法要追加到哪个类上,上面的例子代表该LengthNullable方法要追加到string类中去;第三部份是该类的实例名。
public static class ExtentionMethods
{
public static void Sleep(this Ren r)
{
Console.WriteLine(r.Name+" is sleeping…..");
}
}
public class Ren
{
public string Name { get; set; }
public int Age { get; set; }
public void Speak()
{
Console.WriteLine(Name + Age);
}
}
如果扩展方法与实例方法重名了,那在调用的时候只会调用到实例方法。
7.匿名方法
在C#2.0中就存在匿名方法,但很少有程序员去使用匿名方法,因为它的语法有些怪异。匿名方法的主要用法:使用代理来替代一些简单的方法。
大家都知道代理是指向方法的指针。如:
//声明代理
delegate void Delegate(int x);
//定义方法
void DoSomething(int y) { /* Something */ };
//把代理指向方法
Delegate d = obj.DoSomething;
这里的方法有方法名--DoSomething,而匿名方法可以让代理直接指向一个没有名子的方法。如:
//声明代理
delegate void Delegate(int x);
//把代理指向一个匿名方法。
Del d = delegate(int y) { /* Do Something */ };
匿名方法可以使代码变得更紧凑、更清晰、占用更少的资源。由于匿名方法与代理的关系很密切,所以有的人也称之为“匿名代理”。
在泛型集合List<T>中就有几个方法Exists()、Find()、FindAll()、RemoveAll()等方法,在Array类中也有类似的方法。这些方法都能够对泛型集合进行简单的查询操作,它们的方法签名如下所示:
《图3》
每个方法中都有个参数Predicate<T>,这个参数是个泛型代理,用来筛选数据。
如果不使用匿名方法,那我们得这样编写代码:
static bool HighUnitPrice(LineItem i)
{
if (i.UnitPrice > 25M)
return true;
else
return false;
}
LineItem obj = Array.Find(LineItems, HighUnitPrice);
先定义一个方法HighUnitPrice(LineItem i),然后在Array.Find()的参数Predicate<T>中调用该方法。如果有了匿名方法就不用再单独定义HighUnitPrice(LineItem i)方法了。
如:
var anon = LineItemsList.Find(
delegate(LineItem i)
{
return i.UnitPrice > = 25M;
}
);
在C#3.0中的好多地方都用到了匿名方法,匿名方法是理解Lambda表达式的基础。
8. Lambda表达式
Lambda表达式就是用很少的代码来实现匿名方法。
语法格式:
参数列表=>表达式
下面我们来看看如何把匿名方法转换为Lambda表达式:
匿名方法:
delegate(LineItem i) { return i.UnitPrice >= 25M; }
第一步:删除关键字delegate,变为(LineItem i) { return i.UnitPrice >= 25M; }
第二步:把花括号{}替的换为Lambda运算符=>,变为 (LineItem i) => return i.UnitPrice >= 25M;
第三步:去掉return和分号,语句变成表达式 (LineItem i) => i.UnitPrice >= 25M
第四步:由于编译器会自动进行类型推断,所以我们还可以把LineItem去掉。 i => i.UnitPrice >= 25M
这样就把匿名方法变成了Lambda表达式。
var anon = LineItemsList.Find( i = > i.UnitPrice > = 25M );
由此可见Lambda表达式是由Lambda运算符分割开的两部份组成。右边部分代表运算的语句块,左边部分代表的是运算需要的参数。
9.标准查询操作(Standard Query Operators SQO)
标准查询操作是对IEnumerable<T>接口追加的一系列的扩展方法。通过这些扩展方法对实现IEnumerable<T>接口的集合、数组进行一系列的查询操作。一些比较常用的扩展方法有Where()/OrderBy()/Select()等,LinQ标准查询操作我们将在后面详细阐述。
LinQ中的from关键字并不是扩展方法,因此它不是SQO。from只是为in关键字后的序列指定一个别名。
C#3.0的查询表达式是以一个或多个“from 别名 in 序列名”子句开始,以select或group子句结束。而join/let/where/orderby等子句是可选的,需要写在from和select/group子句中间。
下面是对内存中List<productList>集合进行查询的例子
var noStock = from p in productList
where p.UnitsInStock == 0
orderby p.Category, p.ProductID
select new { p.ProductID, p.Category, p.ProductName };
编译器会自动把上面的表达式语句翻译成下面的链式方法调用,在链式方法调用中使用的就是lambda表达式
var noStock = productList
.Where(p = > p.UnitsInStock == 0)
.OrderBy(p = > p.Category)
.ThenBy(p = > p.ProductID)
.Select(p = > new { p.ProductID, p.Category, p.ProductName });
10.IQueryable<T>接口
IQueryable<T>类型不是集合,他们是支持多态、优化、动态查询功能的LinQ查询序列,它能够把标准化查询操作(SQO)转换成表达式树。简而言之,IQueryable<T>接收的对象不是集合,而是查询表达式树。
我们可以使用IQueryable<T>的ToList()方法来把查询序列变成List<T>类型,使用ToArray()方法把查询序列变成数组类型。
IEnumerable<T>接口中有个AsQueryable()方法,该方法返回的是也IQueryable<T>类型。