13查询方法
查询语句与查询方法的对比
C#中创建查询的方式有两种:查询语法和查询方法。值的注意的是,两种方法的密切相关性超乎想象。原因在于查询语法被编译为对查询方法的调用。因此,当编写如下代码时:
where x<10
编译器将其转换为:
where(x=> x<10)
因此创建查询的两种方式最终会变得相同。两种方式相同,因此自然会引发如下问题:那种方式适合于C#程序?回答如下:一般来说,您会希望使用查询语法。查询语法与C#语言完全集成,通过关键字获得支持,并且更为清晰易懂。
13.1基本的查询方法
System.Linq.Enumerable定义了查询方法,这些方法实现为扩展IEnumerable<T>的功能的扩展方法(System.Linq.Queryable也定义了查询方法,这些查询方法扩展IQueryable<T>的功能)。扩展方法为另一个类添加功能,但是不使用继承性。C#3.0增加了对扩展方法的支持,在本章后面将详细介绍扩展方法。目前,只需要理解只可以对实现IEnumerable<T>的对象调用查询方法。
Enumerable类提供了许多查询方法,但是核心的查询方法对应于本章前面描述的查询关键字。
查询关键字 | 对应的查询方法 |
select | Select(arg) |
where | Where(arg) |
orderby | Orderby(arg)或OrderByDescending(arg) |
join | Join(seq2,key1,key2,result) |
group | GroupBy(arg) |
除了Join查询方法之外,其他查询方法都采用一个实参arg作为参数,arg是Func<T,TResult>类型的对象,Func<T,TResult>是LINQ定义的委托类型,其声明类似于如下:
delete TResult Func<T,TResult>(T arg)
其中,TResult指定委托的结果,T指定参数类型。在查询方法中,arg确定查询方法执行的操作。n=> n>0为Lambad表达式
var posNames=nums.Where(n => n>0);
Join()方法采用4个实参。第一个实参是连接的第二个序列的引用。第一个序列是对其调用Join()方法的序列。通过key1传递第一个序列的键选择器。key2传递第二个序列的键选择器。result则描述了连接的结果。key1的类型是Func<TOuter,TKey>,key2的类型是Func<TInner,TKey>,result的类型则是Func<TOuter,TInner,TResult>。其中,TOuter是主调序列的元素类型,TInner是传递的序列的元素类型,TResult是作为结果的元素的类型。该方法返回包含连接结果的可枚举对象。
var inStockList = from item in items
join entry in statusList
on item.ItemNumber equals entry.ItemNumber
select new Temp(item.Name, entry.InStock);
//上面的写法可以改为下面的形式
var inStockList = items.Join(statusList, k1 => k1.ItemNumber, k2 => k2.ItemNumber,
(k1, k2) => new { k1.Name, k2.InStock });
更多查询相关的扩展方法:
方法 | 说明 |
All(condition) | 如果序列中所有元素满足指定的条件,该方法就返回true |
Any(condition) | 如果序列中的任何元素满足指定的条件,该方法就返回true |
Average() | 返回数字序列中值的平均值 |
Contains(obj) | 如果序列包含指定的对象,就返回true |
Count | 返回序列的长度,这是该序列包含的元素的数量 |
First() | 返回序列中第一个元素 |
Last() | 返回序列中最后一个元素 |
Max() | 返回序列中最大值 |
Min() | 返回序列中最小值 |
Sum() | 返回数组序列中的值的和 |
14延期执行查询和立即执行查询
在LINQ中,查询有两种不同的执行模式:立即执行和延期执行。前面解释过,查询定义了一组规则,知道foreach语句执行时才会是基地执行这组规则。这种情况成为延期执行。
然而,如果使用产生不连续结果的某种扩展方法,则必须执行查询以获得结果。例如,考虑Count()方法。为了是Count()方法返回序列中的元素数量,必须在调用Count()时自动执行查询。这种情况成为立即执行,即立即执行查询以获得结果。因此,即使没有在foreach循环中显示地使用查询,仍然会执行该查询。
int[] nums = { 1, -2, 3, 0, -4, 5 };
int length = (from n in nums
where n > 0
select n).Count();
15 表达式树
表达式树是另一个新的LINQ相关功能,它将拉姆达表达式表示为数据。因此,表达式树自身不可以执行。然而,可以将其转换为可执行形式。System.Linq.Expressions.Expression<T>类封装了表达式树。如果查询由程序外部的对象(如使用SQL的数据库)执行,则表达式树就非常有用。通过将查询表示为数据,可以将该查询转换为数据库能够理解的形式。例如,Visual C#提供LINQ to SQL功能就使用了该程序。因此,表达式树有助于C#支持各种各样的数据源。
可以通过调用Expression定义的Compile()方法获得表达式树的可执行形式,该方法返回可以赋给委托然后执行的引用。可以声明自己的委托类型或使用System名称空间中定义的一种预定义Func类型委托。下面是该委托类型的所有形式的列表:
delegate TResult Func<TResult>();
delegate TResult Func<T1,TResult>();
delegate TResult Func<T1,T2,TResult>();
delegate TResult Func<T1,T2,T3,TResult>();
delegate TResult Func<T1,T2,T3,T4,TResult>();
这些形式表示的方法会返回值并采用0到4个参数(这些参数的类型是T1到T4)。如果表达式要求多于4个参数,则需要定义自己的委托类型。
表达式树有一个关键的限制:表达式树是可以表示表达式拉姆达,而不可以用于表示语句拉姆达。
Expression<Func<int, int, bool>> IsFactorExp = (n, d) => (d != 0) ? (n % d) == 0 : false;
Func<int, int, bool> IsFactor = IsFactorExp.Compile();
if (!IsFactor(10, 5))
Console.WriteLine("5 is a factor of10.");
if (!IsFactor(10,7))
Console.WriteLine("7 is a factor of10.");
Console.Read();
该程序说明了使用表达式树德两个关键步骤。首先,它通过使用如下语句创建表达式树:
Expression<Func<int, int, bool>> IsFactorExp = (n, d) => (d != 0) ? (n % d) == 0 : false;
该语句在内存中构造拉姆达表达式的表示。本章前面已解释过,这种表示是数据而非代码。IsFactorExp引用该表示。下面的语句将表达式数据转换为可执行代码:
Func<int, int, bool> IsFactor = IsFactorExp.Compile();
该语句执行之后,可以调用IsFactory委托以确定一个值是否是另一个值的因子。
16扩展方法
扩展方法提供了可以向类添加功能而无需使用普通继承机制的方式。虽然通常不会常见自己的扩展方法(因为继承机制在许多情况下提供了更佳的解决方案),但是仍然有必要理解扩展方法的工作原理,因为它们是构成LINQ的重要组成部分。
扩展方法是静态方法,必须包含在静态非泛型类中。扩展方法的第一个参数的类型确定可以对其调用该扩展方法的对象的类型。 此外,必须通过this修饰第一个参数。对其调用扩展方法的对象自动传递给第一个参数,但是没有显式地传入实参列表中。
下面是扩展方法的一般形式:
static ret-type name(this invoked-on-type ob,param-list)
当然,如果除了隐式传递给ob的一个实参,之外没有其他实参,则param-list将为空。记住,自动向第一个参数传递对其调用该方法的对象。一般来说,扩展方法将是其所在类的共有成员。
namespace work
{
static class MyExtMeths
{
public static double Revciprocal(this double v)
{
return 1.0 / v;
}
public static string RevCase(this string str)
{
string temp = "";
foreach (char ch in str)
{
if (char.IsLower(ch))
temp += char.ToUpper(ch);
else
temp += char.ToLower(ch);
}
return temp;
}
public static double AbsDiviedBy(this double n, double d)
{
return Math.Abs(n / d);
}
}
class NTest
{
static void Main(string[] args)
{
double val = 8.0;
string str = "Alpha Beta Gamma";
Console.WriteLine("Reciprocal of {0} is {1}", val, val.Revciprocal());
Console.WriteLine(str + " after reversing case is " + str.RevCase());
Console.WriteLine("Result of val.AbsDiviedBy(-2):" + val.AbsDiviedBy(-2));
Console.Read();
}
}
}