深入讲解C#编程中嵌套类型和匿名类型的定义与使用
嵌套类型
在类或结构内部定义的类型称为嵌套类型。例如:
classContainer { classNested { Nested(){} } }
不管外部类型是类还是结构,嵌套类型均默认为private,但是可以设置为public、protectedinternal、protected、internal或private。在上面的示例中,Nested对外部类型是不可访问的,但可以设置为public,如下所示:
classContainer { publicclassNested { Nested(){} } }
嵌套类型(或内部类型)可访问包含类型(或外部类型)。若要访问包含类型,请将其作为构造函数传递给嵌套类型。例如:
publicclassContainer { publicclassNested { privateContainerparent; publicNested() { } publicNested(Containerparent) { this.parent=parent; } } }
嵌套类型可以访问其包含类型可以访问的所有成员。它可以访问包含类型的私有成员和受保护成员(包括所有继承的受保护成员)。
在前面的声明中,类Nested的完整名称为Container.Nested。这是用来创建嵌套类新实例的名称,如下所示:
Container.Nestednest=newContainer.Nested();
匿名类型
匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。类型名由编译器生成,并且不能在源代码级使用。每个属性的类型由编译器推断。
可通过使用new运算符和对象初始值创建匿名类型。
以下示例显示了用两个名为Amount和Message的属性进行初始化的匿名类型。
varv=new{Amount=108,Message="Hello"}; //Restthemousepointeroverv.Amountandv.Messageinthefollowing //statementtoverifythattheirinferredtypesareintandstring. Console.WriteLine(v.Amount+v.Message);
匿名类型通常用在查询表达式的select子句中,以便返回源序列中每个对象的属性子集。
匿名类型包含一个或多个公共只读属性。包含其他种类的类成员(如方法或事件)为无效。用来初始化属性的表达式不能为null、匿名函数或指针类型。
最常见的方案是用其他类型的属性初始化匿名类型。在下面的示例中,假定名为Product的类存在。类Product包括Color和Price属性,以及你不感兴趣的其他属性。变量products是Product对象的集合。匿名类型声明以new关键字开始。声明初始化了一个只使用Product的两个属性的新类型。这将导致在查询中返回较少数量的数据。
如果你没有在匿名类型中指定成员名称,编译器会为匿名类型成员指定与用于初始化这些成员的属性相同的名称。必须为使用表达式初始化的属性提供名称,如下面的示例所示。在下面示例中,匿名类型的属性名称都为Color和Price。
varproductQuery= fromprodinproducts selectnew{prod.Color,prod.Price}; foreach(varvinproductQuery) { Console.WriteLine("Color={0},Price={1}",v.Color,v.Price); }
通常,当你使用匿名类型来初始化变量时,可以通过使用var将变量作为隐式键入的本地变来变量进行声明。类型名称无法在变量声明中给出,因为只有编译器能访问匿名类型的基础名称。有关var的详细信息,请参阅隐式类型的局部变量(C#编程指南)。
可通过将隐式键入的本地变量与隐式键入的数组相结合创建匿名键入的元素的数组,如下面的示例所示。
varanonArray=new[]{new{name="apple",diam=4},new{name="grape",diam=1}};
备注
匿名类型是直接从对象派生的类类型,并且其无法强制转换为除对象外的任意类型。虽然你的应用程序不能访问它,编译器还是提供了每一个匿名类型的名称。从公共语言运行时的角度来看,匿名类型与任何其他引用类型没有什么不同。
如果程序集中的两个或多个匿名对象初始值指定了属性序列,这些属性采用相同顺序且具有相同的名称和类型,则编译器将对象视为相同类型的实例。它们共享同一编译器生成的类型信息。
无法将字段、属性、时间或方法的返回类型声明为具有匿名类型。同样,你不能将方法、属性、构造函数或索引器的形参声明为具有匿名类型。要将匿名类型或包含匿名类型的集合作为参数传递给某一方法,可将参数作为类型对象进行声明。但是,这样做会使强类型化作用无效。如果必须存储查询结果或者必须将查询结果传递到方法边界外部,请考虑使用普通的命名结构或类而不是匿名类型。
由于匿名类型上的Equals和GetHashCode方法是根据方法属性的Equals和GetHashCode定义的,因此仅当同一匿名类型的两个实例的所有属性都相等时,这两个实例才相等。
在查询中返回元素属性的子集
当下列两个条件都满足时,可在查询表达式中使用匿名类型:
您只想返回每个源元素的某些属性。
您无需在执行查询的方法的范围之外存储查询结果。
如果您只想从每个源元素中返回一个属性或字段,则只需在select子句中使用点运算符。例如,若要只返回每个student的ID,可以按如下方式编写select子句:
selectstudent.ID;
下面的示例演示如何使用匿名类型只返回每个源元素的符合指定条件的属性子集。
privatestaticvoidQueryByScore() { //Createthequery.varisrequiredbecause //thequeryproducesasequenceofanonymoustypes. varqueryHighScores= fromstudentinstudents wherestudent.ExamScores[0]>95 selectnew{student.FirstName,student.LastName}; //Executethequery. foreach(varobjinqueryHighScores) { //Theanonymoustype'spropertieswerenotnamed.Therefore //theyhavethesamenamesastheStudentproperties. Console.WriteLine(obj.FirstName+","+obj.LastName); } }
输出:
Adams,Terry Fakhouri,Fadi Garcia,Cesar Omelchenko,Svetlana Zabokritski,Eugene
请注意,如果未指定名称,则匿名类型将使用源元素的名称作为其属性名称。若要为匿名类型中的属性指定新名称,请按如下方式编写select语句:
selectnew{First=student.FirstName,Last=student.LastName};
如果您在上一个示例中这样做,则Console.WriteLine语句也必须更改:
Console.WriteLine(student.First+""+student.Last);