第08部分 其他关键字
8.1 方法参数
- 为不具有 in、ref 或 out 的方法声明的参数会按值传递给调用的方法。 可以在方法中更改该值,但当控制传递回调用过程时,不会保留更改后的值。 可以通过使用方法参数关键字更改此行为。
8.1.1 params
- 使用 params 关键字可以指定采用数目可变的参数的方法参数。 参数类型必须是一维数组。
- 在方法声明中的 params 关键字之后不允许有任何其他参数,并且在方法声明中只允许有一个 params 关键字。
- 如果 params 参数的声明类型不是一维数组,则会发生编译器错误 CS0225。
- 使用 params 参数调用方法时,可以传入:
- 数组元素类型的参数的逗号分隔列表。
- 指定类型的参数的数组。
- 无参数。 如果未发送任何参数,则 params 列表的长度为零。
8.1.2 in
- in 关键字通过引用传递参数。 它让形参成为实参的别名,这必须是变量。 换而言之,对形参执行的任何操作都是对实参执行的。 它类似于 ref 或 out 关键字,不同之处在于 in 参数无法通过调用的方法进行修改。
- 作为 in 参数传递的变量在方法调用中传递之前必须进行初始化。
- in、ref 和 out 关键字不被视为用于重载决议的方法签名的一部分。 因此,如果唯一的不同是一个方法采用 ref 或 in 参数,而另一个方法采用 out 参数,则无法重载这两个方法。
8.1.3 in重载决策
- 通过理解使用 in 参数的动机,可以理解使用按值方法和使用 in 参数方法的重载决策规则。 定义使用 in 参数的方法是一项潜在的性能优化。 某些 struct 类型参数可能很大,在紧凑的循环或关键代码路径中调用方法时,复制这些结构的成本就很高。 方法声明 in 参数以指定参数可能按引用安全传递,因为所调用的方法不修改该参数的状态。 按引用传递这些参数可以避免(可能产生的)高昂的复制成本。
- 为调用站点上的参数指定 in 通常为可选。 按值传递参数和使用 in 修饰符按引用传递参数这两种方法并没有语义差异。 可以在调用站点选择 in 修饰符,因为不需要指出参数值可能会改变。 在调用站点显式添加 in 修饰符以确保参数是按引用传递,而不是按值传递。 显式使用 in 有以下两个效果:
- 在调用站点指定 in 会强制编译器选择使用匹配的 in 参数定义的方法。 否则,如果两种方法唯一的区别在于是否存在 in,则按值重载的匹配度会更高。
- 指定 in 会声明你想按引用传递参数。 结合 in 使用的参数必须代表一个可以直接引用的位置。 out 和 ref 参数的相同常规规则适用:不得使用常量、普通属性或其他生成值的表达式。 否则,在调用站点省略 in 就会通知编译器你将允许它创建临时变量,并按只读引用传递至方法。 编译器创建临时变量以克服一些 in 参数的限制:
- 临时变量允许将编译时常数作为 in 参数。
- 临时变量允许使用属性或 in 参数的其他表达式。
- 存在从实参类型到形参类型的隐式转换时,临时变量允许使用实参。
static void Method(in int argument)
{
// implementation removed
}
Method(5); //创造临时变量int 5
Method(5L); // error:long无法转换int
short s = 0;
Method(s); //创造临时变量int 0
Method(in s); //error:short无法转换int
int i = 42;
Method(i); // 只读引用
Method(in i); // 只读引用,明确使用in修饰的变量
为了简化操作,前面的代码将 int 用作参数类型。 因为大多数新式计算机中的引用都比 int 大,所以将单个 int 作为只读引用传递没有任何好处。
8.1.4 in参数的限制
- 不能将 in、ref 和 out 关键字修饰参数用于以下几种方法:
- 异步方法,通过使用 async 修饰符定义。
- 迭代器方法,包括 yield return 或 yield break 语句。
- 扩展方法的第一个参数不能有 in 修饰符,除非该参数是结构。
- 扩展方法的第一个参数,其中该参数是泛型类型(即使该类型被约束为结构。)
8.1.5 ref
- ref 关键字指示按引用传递的值。 它用在四种不同的上下文中:
- 在方法签名和方法调用中,按引用将参数传递给方法。
- 在方法签名中,按引用将值返回给调用方。
- 在成员正文中,指示引用返回值是否作为调用方欲修改的引用被存储在本地,或在一般情况下,局部变量按引用访问另一个值。
- 在 struct 声明中声明 ref struct 或 readonly ref struct。
8.1.6 ref按引用传递参数
- 在方法的参数列表中使用 ref 关键字时,它指示参数按引用传递,而非按值传递。 ref 关键字让形参成为实参的别名,这必须是变量。 换而言之,对形参执行的任何操作都是对实参执行的。
- 不要混淆通过引用传递的概念与引用类型的概念。 这两种概念是不同的。 无论方法参数是值类型还是引用类型,均可由 ref 修改。 当通过引用传递时,不会对值类型装箱。
- 若要使用 ref 参数,方法定义和调用方法均必须显式使用 ref 关键字
void Method(ref int refArgument)
{
refArgument = refArgument + 44;
}
int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45
- 传递到 ref 或 in 形参的实参必须先经过初始化,然后才能传递。 这与 out 形参不同,在传递之前,不需要显式初始化该形参的实参。
- 类的成员不能具有仅在 ref、in 或 out 方面不同的签名。 如果类型的两个成员之间的唯一区别在于其中一个具有 ref 参数,而另一个具有 out 或 in 参数,则会发生编译器错误。但是,当一个方法具有 ref、in 或 out 参数,另一个方法具有值参数时,则可以重载方法。
class CS0663_Example
{
// Compiler error CS0663: "Cannot define overloaded
// methods that differ only on ref and out".
public void SampleMethod(out int i) { }
public void SampleMethod(ref int i) { }
}
class RefOverloadExample
{
public void SampleMethod(int i) { }
public void SampleMethod(ref int i) { }
}
- 属性不是变量。 它们是方法,不能传递到 ref 参数。
- 不能将 ref、in 和 out 关键字用于以下几种方法:
- 异步方法,通过使用 async 修饰符定义。
- 迭代器方法,包括 yield return 或 yield break 语句。
此外,扩展方法具有以下限制:
- 不能对扩展方法的第一个参数使用 out 关键字。
- 当参数不是结构或是不被约束为结构的泛型类型时,不能对扩展方法的第一个参数使用 ref 关键字。
- 除非第一个参数是结构,否则不能使用 in 关键字。 即使约束为结构,也不能对任何泛型类型使用 in 关键字。
8.1.7 ref引用返回值
- 引用返回值(或 ref 返回值)是由方法按引用向调用方返回的值。 即是说,调用方可以修改方法所返回的值,此更改反映在调用方法中的对象的状态中。
- 使用 ref 关键字来定义引用返回值:
- 在方法签名中。
public ref decimal GetCurrentPrice()
- 在 return 标记和方法的 return 语句中返回的变量之间。
return ref DecimalArray[0];
- 为方便调用方修改对象的状态,引用返回值必须存储在被显式定义为 ref 局部变量的变量中。
public static ref int Find(int[,] matrix, Func<int, bool> predicate)
{
for (int i = 0; i < matrix.GetLength(0); i++)
for (int j = 0; j < matrix.GetLength(1); j++)
if (predicate(matrix[i, j]))
return ref matrix[i, j];
throw new InvalidOperationException("Not found");
}
- 所调用方法还可能会将返回值声明为 ref readonly 以按引用返回值,并坚持调用代码无法修改返回的值。 调用方法可以通过将返回值存储在本地 ref readonly 变量中来避免复制该值。
8.1.8 ref 局部变量
- ref 局部变量用于指代使用 return ref 返回的值。 无法将 ref 局部变量初始化为非 ref 返回值。 也就是说,初始化的右侧必须为引用。 任何对 ref 本地变量值的修改都将反映在对象的状态中,该对象的方法按引用返回值。
- 在变量声明前或在方法(该方法将按引用返回值)调用前使用 ref 关键字定义 ref 局部变量。
ref decimal estValue = ref Building.GetEstimatedValue();
- 必须在两个位置同时使用 ref 关键字,否则编译器将生成错误 CS8172:“无法使用值对按引用变量进行初始化”。
8.1.9 Ref readonly 局部变量
- Ref readonly 局部变量用于指代在其签名中具有 ref readonly 并使用 return ref 的方法或属性返回的值。 ref readonly 变量将 ref 本地变量的属性与 readonly 变量结合使用:它是所分配到的存储的别名,且无法修改。
8.1.10 out
- out 关键字通过引用传递参数。 它让形参成为实参的别名,这必须是变量。 换而言之,对形参执行的任何操作都是对实参执行的。 它与 ref 关键字相似,只不过 ref 要求在传递之前初始化变量。 它也类似于 in 关键字,只不过 in 不允许通过调用方法来修改参数值。 若要使用 out 参数,方法定义和调用方法均必须显式使用 out 关键字。
- 作为 out 参数传递的变量在方法调用中传递之前不必进行初始化。 但是,被调用的方法需要在返回之前赋一个值。
8.2 命名空间关键字
8.2.1 命名空间
- namespace 关键字用于声明包含一组相关对象的作用域。 可以使用命名空间来组织代码元素并创建全局唯一类型。
- 在命名空间中,可以声明零个或多个以下类型:另一个命名空间,class,interface,struct,enum,delegate。
8.2.2 Using
- using 关键字有三个主要用途:
- using 语句定义一个范围,在此范围的末尾将释放对象。
- using 指令为命名空间创建别名,或导入在其他命名空间中定义的类型。
- using static 指令导入单个类的成员。
8.2.3 using 指令
- 允许在命名空间中使用类型,这样无需在该命名空间中限定某个类型的使用:
using System.Text;
- 为命名空间或类型创建别名。 这称为 using 别名指令。
using Project = PC.MyCompany.Project;
- using 指令的范围限于显示它的文件。
可能出现 using 指令的位置:
- 源代码文件的开头,位于任何命名空间或类型定义之前。
- 在任何命名空间中,但位于此命名空间中声明的任何命名空间或类型之前。
否则,将生成编译器错误 CS1529。
- 创建 using 别名指令,以便更易于将标识符限定为命名空间或类型。 在任何 using 指令中,都必须使用完全限定的命名空间或类型,而无需考虑它之前的 using 指令。 using 指令的声明中不能使用 using 别名。
using s = System.Text;
using s.RegularExpressions; // Generates a compiler error.
- 创建 using 指令,以便在命名空间中使用类型而不必指定命名空间。 using 指令不为你提供对嵌套在指定命名空间中的任何命名空间的访问权限。
- 命名空间分为两类:用户定义的命名空间和系统定义的命名空间。 用户定义的命名空间是在代码中定义的命名空间。
- using 别名指令的右侧不能有开放式泛型类型。 例如,不能为 List<T> 创建 using 别名,但可以为 List<int> 创建 using 别名。
8.2.4 using 静态指令
- using static 指令指定一种类型,无需指定类型名称即可访问其静态成员和嵌套类型。
<语法为>
using static <fully-qualified-type-name>;//完全限定名称
using static System.Console;
using static System.Math;
class Program
{
static void Main()
{
WriteLine(Sqrt(3*3 + 4*4));
}
}
- using static 指令将生成更简洁的代码,也可使指定类型中声明的扩展方法可用于扩展方法查找。
8.2.5 using语句
- using语句提供可确保正确使用 IDisposable 对象的方便语法。 从 C#8.0 开始,using 语句可确保正确使用 IAsyncDisposable 对象。
8.2.6 外部别名
-
extern声明:当引用具有相同的完全限定类型名称的程序集的两个版本,可通过外部程序集别名。
-
extern也可用于声明在非托管代码中编写的方法。引用声明之前,要先用VS向项目中添加dll文件引用。
8.3 类型测试关键字is
- is 运算符检查表达式的结果是否与给定类型兼容
8.3.1 利用 is 的模式匹配
- 类型模式:用于测试表达式是否可转换为指定类型,如果可以,则将其转换为该类型的一个变量。
expr is type varname
其中 expr 是计算结果为某个类型的实例的表达式,type 是 expr 结果要转换到的类型的名称,varname 是 expr 结果要转换到的对象(如果 is 测试为 true)。
如果 expr 不为 null 且以下任意条件为 true,那么 is 表达式为 true:
- expr 是与 type 具有相同类型的一个实例。
- expr 是派生自 type 的类型的一个实例。 换言之,expr 结果可以向上转换为 type 的一个实例。
- expr 具有属于 type 的一个基类的编译时类型,expr 还具有属于 type 或派生自 type 的运行时类型。 变量的编译时类型是其声明中定义的变量类型。 变量的运行时类型是分配给该变量的实例类型。
- expr 是实现 type 接口的类型的一个实例。
- 常量模式:使用常量模式执行模式匹配时,is 会测试表达式结果是否等于指定常量。
expr is constant
其中 expr 是要计算的表达式,constant 是要测试的值。 constant 可以是以下任何常数表达式:null、文字值、已声明 const 变量的名称、一个枚举常量。
常数表达式的计算方式如下:
- 如果 expr 和 constant 均为整型类型,则 C# 相等运算符确定表示式是否返回 true(即,是否为 expr == constant)。
- 否则,由对静态 Object.Equals(expr, constant) 方法的调用确定表达式的值。
- var模式
expr is var varname
- 其中,expr 的值始终分配给名为 varname 的局部变量 。 varname 变量的类型与 expr 的编译时类型相同 。
- 如果 expr 的计算结果为 null,则 is 表达式将生成 true 并将 null 分配给 varname 。 var 模式是 is 对 null 值生成 true 的少数用途之一。
- 可以使用 var 模式在布尔表达式中创建临时变量。
8.4 泛型类型约束关键字
8.4.1 new约束
- new 约束指定泛型类声明中的类型实参必须有公共的无参数构造函数。 若要使用 new 约束,则该类型不能为抽象类型。
class ItemFactory<T> where T : new()
{
public T GetNewItem()
{
return new T();
}
}
- 当与其他约束一起使用时,new() 约束必须最后指定:
public class ItemFactory2<T>
where T : IComparable, new()
{ }
8.4.2 where约束
- 泛型定义中的 where 子句指定对用作泛型类型、方法、委托或本地函数中类型参数的参数类型的约束。 约束可指定接口、基类或要求泛型类型为引用、值或非托管类型。 它们声明类型参数必须具备的功能。
- where 子句还可包括基类约束。 基类约束表明用作该泛型类型的类型参数的类型具有指定的类作为基类(或者是该基类)。 该基类约束一经使用,就必须出现在该类型参数的所有其他约束之前。 某些类型不允许作为基类约束:Object、Array 和 ValueType。 在 C# 7.3 之前,Enum、Delegate 和 MulticastDelegate 也不允许作为基类约束。
public class UsingEnum<T> where T : System.Enum { }
public class UsingDelegate<T> where T : System.Delegate { }
public class Multicaster<T> where T : System.MulticastDelegate { }
- where 子句可指定类型为 class 或 struct。 struct 约束不再需要指定 System.ValueType 的基类约束。 System.ValueType 类型可能不用作基类约束。 以下示例显示 class 和 struct 约束:
class MyClass<T, U>
where T : class
where U : struct
{ }
- where 子句还可包括 unmanaged 约束。 unmanaged 约束将类型参数限制为名为“非托管类型”的类型。
class UnManagedWrapper<T>
where T : unmanaged
{ }
- where 子句也可能包括构造函数约束 new()。 该约束使得能够使用 new 运算符创建类型参数的实例。 new() 约束可以让编译器知道:提供的任何类型参数都必须具有可访问的无参数构造函数。
public class MyGenericClass<T> where T : IComparable<T>, new()
{
// The following line is not possible without new() constraint:
T item = new T();
}
new() 约束出现在 where 子句的最后。 new() 约束不能与 struct 或 unmanaged 约束结合使用。 所有满足这些约束的类型必须具有可访问的无参数构造函数。
8.5 访问修饰符
8.5.1 base
- base 关键字用于从派生类中访问基类的成员:
- 调用基类上已被其他方法重写的方法。
- 指定创建派生类实例时应调用的基类构造函数。
- 仅允许基类访问在构造函数、实例方法或实例属性访问器中进行。
- 从静态方法中使用 base 关键字是错误的。
- 所访问的基类是类声明中指定的基类。 例如,如果指定 class ClassB : ClassA,则从 ClassB 访问 ClassA 的成员,而不考虑 ClassA 的基类。
8.5.2 this
- this 关键字指代类的当前实例,还可用作扩展方法的第一个参数的修饰符。
限定类似名称隐藏的成员。
public class Employee
{
private string alias;
private string name;
public Employee(string name, string alias)
{
// Use this to qualify the members of the class
// instead of the constructor parameters.
this.name = name;
this.alias = alias;
}
}
将对象作为参数传递给方法
CalcTax(this);
声明索引器
public int this[int param]
{
get { return array[param]; }
set { array[param] = value; }
}
静态成员函数,因为它们存在于类级别且不属于对象,不具有 this 指针。 在静态方法中引用 this 会生成错误。
8.6 文字关键字
8.6.1 null
- null 关键字是表示不引用任何对象的空引用的文字值。 null 是引用类型变量的默认值。 普通值类型不能为 NULL,可为空的值类型除外。
8.6.2 true和false
- bool 类型关键字是 .NET System.Boolean 结构类型的别名,它表示一个布尔值,可为 true 或 false。
8.6.3 default
- default 关键字有两种用法:
- 指定 switch 语句中的默认标签。
- 作为 default 默认运算符或文本生成类型的默认值。
8.7 上下文关键字
- 上下文关键字用于在代码中提供特定含义,但它不是 C# 中的保留字。
关键字 | 说明 |
---|---|
add | 定义一个自定义事件访问器,客户端代码订阅事件时会调用该访问器。 |
asybc | 指示修改后的方法、lambda 表达式或匿名方法是异步的。 |
await | 挂起异步方法,直到等待的任务完成。 |
dynamic | 定义一个引用类型,实现发生绕过编译时类型检查的操作。 |
ge | 为属性或索引器定义访问器方法。 |
global | 未以其他方式命名的全局命名空间的别名。 |
partial | 在整个同一编译单元内定义分部类、结构和接口。 |
remove | 定义一个自定义事件访问器,客户端代码取消订阅事件时会调用该访问器。 |
set | 为属性或索引器定义访问器方法。 |
value | 用于设置访问器和添加或删除事件处理程序。 |
var | 使编译器能够确定在方法作用域中声明的变量类型。 |
when | 指定 catch 块的筛选条件或 switch 语句的 case 标签。 |
where | 将约束添加到泛型声明。 |
yield | 在迭代器块中使用,用于向枚举数对象返回值或用于表示迭代结束。 |
8.7.1 add与remove
- 用于定义一个在客户端代码订阅或取消你的事件时调用的自定义事件访问器
class Events : IDrawingObject
{
event EventHandler PreDrawEvent;
event EventHandler IDrawingObject.OnDraw
{
add => PreDrawEvent += value;
remove => PreDrawEvent -= value;
}
}
8.7.2 get与set
- get 关键字在属性或索引器中定义访问器方法,它将返回属性值或索引器元素。
- set 关键字在属性或索引器中定义访问器,它会向属性或索引器元素分配值。\
- 自动实现:
class TimePeriod2
{
public double Hours { get; set; }
}
8.7.3 partial分部
- 分部类型:通过分部类型可以定义要拆分到多个文件中的类、结构、接口或记录。
namespace PC
{
partial class A
{
int num = 0;
void MethodA() { }
partial void MethodC();
}
}
//在另一个class文件里
namespace PC
{
partial class A
{
void MethodB() { }
partial void MethodC() { }
}
}
- 分部方法的原理和分部类型一致,分部方法在分部类型的一部分中定义了签名,并在该类型的另一部分中定义了实现。 通过分部方法,类设计器可提供与事件处理程序类似的方法挂钩,以便开发者决定是否实现。 如果开发者不提供实现,则编译器在编译时删除签名。 以下条件适用于分部方法:
- 分部类型各部分中的签名必须匹配。
- 方法必须返回 void。
- 不允许使用访问修饰符。 分部方法是隐式专用的。
8.7.4 when
- 可以使用上下文关键字 when 在以下上下文中指定筛选条件:
- 在 try/catch 或 try/catch/finally 块的 catch 语句中。
- 在 switch 语句的 case 标签中。
- 在 switch 表达式中。
8.7.5 value
- 上下文关键字 value 在属性和索引器声明的 set 访问器中使用。 此关键字类似于方法的输入参数。 关键字 value 引用客户端代码尝试分配给属性或索引器的值。
private int num;
public virtual int Number
{
get { return num; }
set { num = value; }
}
8.7.6 yield
- 在语句中使用 yield 上下文关键字,则意味着它在其中出现的方法、运算符或 get 访问器是迭代器。 通过使用 yield 定义迭代器,可在实现自定义集合类型的 IEnumerator<T> 和 IEnumerable 模式时无需其他显式。
yield 语句的两种形式
yield return <expression>;
yield break;
迭代器方法和 get 访问器
-
迭代器的声明必须满足以下要求:
- 返回类型必须为 IEnumerable、IEnumerable<T>、IEnumerator 或 IEnumerator<T>。
- 声明不能有任何 in、ref 或 out 参数。
- 返回 yield 或 IEnumerable 的迭代器的 IEnumerator 类型为 object。 如果迭代器返回 IEnumerable<T> 或 IEnumerator<T>,则必须将 yield return 语句中的表达式类型隐式转换为泛型类型参数。
-
以下情形中不能包含 yield return 或 yield break 语句:
- Lambda 表达式和匿名方法。
- 包含不安全的块的方法。
异常处理
- 不能将 yield return 语句置于 try-catch 块中。 可将 yield return 语句置于 try-finally 语句的 try 块中。可将 yield break 语句置于 try 块或 catch 块中,但不能将其置于 finally 块中。如果 foreach 主体(在迭代器方法之外)引发异常,则将执行迭代器方法中的 finally 块。
8.8 查询关键字
子句 | 说明 |
---|---|
from | 指定数据源和范围变量(类似于迭代变量)。 |
where | 基于由逻辑 AND 和 OR 运算符(&&或||)分隔的一个或多个布尔表达式筛选源元素。 |
select | 指定执行查询时,所返回序列中元素的类型和形状。 |
group | 根据指定的密钥值对查询结果分组。 |
into | 提供可作为对 join、group 或 select 子句结果引用的标识符。 |
orderby | 根据元素类型的默认比较器对查询结果进行升序或降序排序。 |
join | 基于两个指定匹配条件间的相等比较而联接两个数据源。 |
let | 引入范围变量,在查询表达式中存储子表达式结果。 |
in | join 子句中的上下文关键字。 |
on | join子句中的上下文关键字。 |
equals | join 子句中的上下文关键字。 |
by | group 子句中的上下文关键字。 |
ascending | orderby 子句中的上下文关键字。 |
descending | orderby子句中的上下文关键字。 |
常规
class LowNums
{
static void Main()
{
// A simple data source.
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
// Create the query.
// lowNums is an IEnumerable<int>
var lowNums = from num in numbers
where num < 5
select num;
// Execute the query.
foreach (int i in lowNums)
{
Console.Write(i + " ");
}
}
}
// Output: 4 1 3 2 0
8.8.1 from复合
var scoreQuery = from student in students
from score in student.Scores
where score > 90
select new { Last = student.LastName, score };
8.8.2 where
- where 子句是一种筛选机制。 除了不能是第一个或最后一个子句外,它几乎可以放在查询表达式中的任何位置。 where 子句可以出现在 group 子句的前面或后面,具体取决于时必须在对源元素进行分组之前还是之后来筛选源元素。
- 如果指定的谓词对于数据源中的元素无效,则会发生编译时错误。 这是 LINQ 提供的强类型检查的一个优点。
- 在编译时,where 关键字将转换为对 Where 标准查询运算符方法的调用。
8.8.3 select
- 在查询表达式中,select 子句指定在执行查询时产生的值的类型。 根据计算所有以前的子句以及根据 select 子句本身的所有表达式得出结果。 查询表达式必须以 select 子句或 group 子句结尾。
8.8.4 group
- group 子句返回一个 IGrouping<TKey,TElement> 对象序列,这些对象包含零个或更多与该组的键值匹配的项。如果要对每个组执行附加查询操作,可使用上下文关键字 into 指定一个临时标识符。 使用 into 时,必须继续编写该查询,并最终使用一个select 语句或另一个 group 子句结束该查询
// Group students by the first letter of their last name
// Query variable is an IEnumerable<IGrouping<char, Student>>
var studentQuery2 =
from student in students
group student by student.Last[0] into g
orderby g.Key
select g;
- 由于 group 查询产生的 IGrouping<TKey,TElement> 对象实质上是一个由列表组成的列表,因此必须使用嵌套的 foreach 循环来访问每一组中的各个项。 外部循环用于循环访问组键,内部循环用于循环访问组本身包含的每个项。 组可能具有键,但没有元素。
- 键值对可以是任何类型,如字符串、内置数值类型、用户定义的命名类型或匿名类型。
// Iterate group items with a nested foreach. This IGrouping encapsulates
// a sequence of Student objects, and a Key of type char.
// For convenience, var can also be used in the foreach statement.
foreach (IGrouping<char, Student> studentGroup in studentQuery2)
{
Console.WriteLine(studentGroup.Key);
// Explicit type for student could also be used here.
foreach (var student in studentGroup)
{
Console.WriteLine(" {0}, {1}", student.Last, student.First);
}
}
8.8.5 let,by,orderby,into
var studentQuery =
from student in students
let avg = (int)student.Scores.Average()
group student by avg/10 into g
orderby g.Key ascending
select g;
-
通过 let 关键字执行此操作,该关键字创建一个新的范围变量并通过提供的表达式结果初始化该变量。 使用值进行初始化后,范围变量不能用于存储另一个值。
-
orderby 子句可导致返回的序列或子序列(组)以升序或降序排序。 若要执行一个或多个次级排序操作,可以指定多个键。 元素类型的默认比较器执行排序。 默认的排序顺序为升序。 还可以指定自定义比较器。 但是,只适用于使用基于方法的语法。
-
其中ascending表示升序,descending表示降序。
-
使用 into 上下文关键字创建临时标识符,将 group、join 或 select 子句的结果存储至新标识符。
-
by:by 上下文关键字用于在查询表达式的 group 子句中指定应返回项的分组方式。
8.8.6 join,on 和 equals
- join 子句可用于将来自不同源序列并且在对象模型中没有直接关系的元素相关联。 唯一的要求是每个源中的元素需要共享某个可以进行比较以判断是否相等的值。 例如,食品经销商可能拥有某种产品的供应商列表以及买主列表。 例如,可以使用 join 子句创建该产品同一指定地区供应商和买主的列表。
内部联结
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence
分组联接:含有 into 表达式的 join 子句称为分组联接。
var innerGroupJoinQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from prod2 in prodGroup
where prod2.UnitPrice > 2.50M
select prod2;
- on:on 上下文关键字用于在查询表达式的 join 子句中指定联接条件。
- equals:equals 上下文关键字用于在查询表达式的 join 子句中比较两个序列的元素。