配色: 字号:
反射
2017-12-15 | 阅:  转:  |  分享 
  
反射本单要点使用自定义特性在运行期间使用反射检查元数据从支持反射的类中构建访问点本单代码下载地址www.wrox.com/go/procsh
arp在运行期间处理和检查代码自定义特性允许把自定义元数据与程序元素关联起来。这些元数据是在编译过程中创建的,并嵌入到程序集当中
。反射是一个普通术语,它描述了运行过程中检查和处理程序元素的功能。反射可以完成以下任务:枚举类型的成员实例化新对象执行对象的成员查
找类型的信息检查应用于某种类型的自定义特性创建和编译新程序集用到的示例为了说明自定义特性和反射,我们将开发一个例子,说明公司如何定
期升级软件,自动记录升级信息。在这个示例中,要定义几个自定义特性,表示程序元素最后修改的日期,以及发生了什么变化。然后使用反射开发
一个应用程序,它在程序集中查找这些自定义特性,自动显示软件自某个给定日期以来升级的所有信息。另外一个示例是一个应用程序,这个程序从
数据库中读取信息或把信息写入数据库,并使用自定义特性,把类和属性标记为对应的数据库表和列。然后在运行期间从程序集中读取这些特性,使
程序可以自动从数据库的相应位置检索或写入数据,无须为每个表或列编写特性的逻辑。自定义特性.NETFramework允许用户在应用
程序中定义自己的特性,显然,这些特性不会影响编译过程,因为编译器不能识别它们,但是这些特性在应用于程序元素时,可以在编译好的程序集
中用作元数据。这些元数据在文档说明中非常有用。但是,使用自定特性非常强大的因素是使用反射,代码可以读取这些元数据,使用它们在运行期
间作出决策。也就是说,自定义特性可以直接影响代码运行的方式,例如:在开发可扩展架构时,允许加载插件或模块。编写自定义特性为了理解编
写自定义特性的方式,应了解一下在编译器遇到代码中某个应用特性的元素时,该如何处理。以数据库为例,假设有一个C#属性如下:[Fild
Name("SocialSecurityNumber")]publicstringSoclalSecurityNumber
{get;set;}当编译器发现这个属性应用了一个FildName属性时,会在FildName后追加Attribute(变成
FildNameFildName),然后在其搜索路径的所有名称空间(即using引入的名称空间)中搜索有指定名称的类。但要注意,如
果用一个特性标记数据项,而该特性名发Attribute结尾,编译器就不会把Attribute追加到组合名称中。上面的代码等价于:[
FildNameAttribute("SocialSecurityNumber")]publicstringSoclalSe
curityNumber{get;set;}编译器会找到含有该名称的类,且这个类直接或间接派生自System.Attrib
ute。编译器还认为这个类包含控制特性用法的信息。特别是属性类需要指定:特性可以用到哪些类型和程序元素上(类?结构?属性或方法等)
它是否可以多次应用于同一个元素上特性应用到接口或类时,是否由接口和派生类继承这个特性在哪些必选和可选参数如果编译器找不到对应的特性
类,或者找到一个这样的特性类,但使用特性的方式与特性类中的信息不匹配,编译器会产生一个编译错误。继续上面的示例假定定义了一个Fil
dName特性:[AttributeUsage(AttributeTargets.Property,AllowMultiple
=false,Inherited=false)]publicclassFildNameAttribute:Attri
bute{privatestringname;publicFildNameAttribute(stringname)
{this.name=name;}}下面几个小节讨论这个定义中的每个元素AttributeUsage要注意的第一个问题是
特性类本身用一个特性----AttributeUsage特性来标记这是Microsoft定义的一个特性,C#编译器为它提供了特殊
支持(你可以认为AttributeUsage根本不是一个特性,它更像是一个元素特性,因为它只能应用到其它特性上,不能应到到类上)A
ttributeUsage主要用于标识自定义特性可以应到到哪些类型的程序元素上。这些信息由它的第一个参数给出,这个参数是必选的,其
类型是枚举类型AttributeTargets。成员名称描述All可以对任何应用程序元素应用属性Assembly可以对程序集应用属
性。Class可以对类应用属性Constructor可以对构造函数应用属性。Delegate可以对委托应用属性。Enum可以对枚举
应用属性。Event可以对事件应用属性。Field可以对字段应用属性GenericParameter可以对泛型参数应用属性。说明目
前,此特性可以应用仅于C#、Microsoft中间语言(MSIL)和发出的代码。Interface可以对接口应用属性。Me
thod可以对方法应用属性。Module可以对模块应用属性。说明Module指的是可移植的可执行文件(.dll或.exe),
而非VisualBasic标准模块。Parameter可以对参数应用属性。Property可以对属性(Property)
应用属性(Attribute)。ReturnValue可以对返回值应用属性。Struct可以对结构应用属性,即值类型。在上面的列
不中,有两个值不对应于任何程序元素:AssemblyModule。它们可以应用到整个程序集或模块中,而不是应用到代码中的一个元素
上,在这种情况下,这个特性可以放在源代码中的任何一个地方,但需要Assembly或Module作为前缀:[assembly:Som
eAssemblyAttribute(Parameters)][module:SomeAssemblyAttribute(Para
meters)]可以用OR运算符把这些值组合起来。[AttributeUsage(AttributeTargets.Propert
y|AttributeTargets.Interface,AllowMultiple=false,Inherited
=false)]publicclassFildNameAttribute:Attribute{privatestri
ngname;publicFildNameAttribute(stringname){this.name=name
;}}还有一两个参数AllowMultiple和Inherited它们是可选的参数,根据需要可以忽略它们指定特性参数[FildN
ame("SocialSecurityNumber")]publicstringSoclalSecurityNumber{
get;set;}编译器会检查传递给特性的参数,并查找到该特性中带这些参数的构造函数。如果编译器找到一个这样的构造函数,编译器
就会把指定的元数据传递给程序集。如果找不到,就生成一个错误。3指定特性的可选参数通过使用特性类中的公有字段或属性来实现。[Attr
ibuteUsage(AttributeTargets.Property|AttributeTargets.Interface
,AllowMultiple=false,Inherited=false)]publicclassFildNam
eAttribute:Attribute{publicstringComment{get;set;}privat
estringname;publicFildNameAttribute(stringname){this.name
=name;}}我们可以这样指定参数:[FildName("SocialSecurityNumber",Comment="
注释信息")]publicstringSoclalSecurityNumber{get;set;}WhatsNewAt
tributes示例:这个示例提供了一个特性,表示最后一次修改程序元素的时间。这个示例比较复杂,因为它包含三个不同的程序集:Wha
tsNewAttributes程序集,包含特性的定义。VectorClass程序集,包含所应用特性的代码LookUpWhatsNe
w程序集,读取并显示特性信息LookUpWhatsNew是一个控制台应用程序,其余两个都是类库。WhatsNewAttribut
es中有两个特性类:LastModdifiefiedAttribute和SupportsWhatsNewAttributed。Su
pportsWhatsNew是一个较小的类,它表示不带任何参数的特性,这个特性是一个程序集的特性,它用于把程序集标记为通过Last
ModifiedAttribute维护的文档。这样,以后查看这个程序集的程序会知道,它读取的程序集是我们使用自动文档过程生成的那个
程序集。《WhatsNewAttributes》usingSystem;usingSystem.Collections.Gen
eric;usingSystem.Linq;usingSystem.Text;namespaceWahtsNewAttrib
utes{[AttributeUsage(AttributeTargets.Class|AttributeTargets.M
ethod,AllowMultiple=true,Inherited=false)]publicclassLas
tModifiedAttribute:Attribute{privatereadonlyDateTime_dateMod
ified;privatereadonlystring_changes;publicDateTimeDateModi
fied{get{return_dateModified;}}publicstringChanges{ge
t{return_changes;}}publicstringIssuse{get;set;}publi
cLastModifiedAttribute(string_dateModified,string_changes){
this._dateModified=DateTime.Parse(_dateModified);this._changes
=_changes;}}[AttributeUsage(AttributeTargets.Assembly)]pub
licclassSupportsWhatsNewAttribute:Attribute{}}《VectorClass》us
ingSystem;usingSystem.Collections;usingSystem.Collections.Gene
ric;usingSystem.Linq;usingSystem.Text;usingWahtsNewAttributes;
[assembly:SupportsWhatsNew]namespaceVectorClass{classVector:
IFormattable,IEnumerable{publicdoublex,y,z;publicVector(
doublex,doubley,doublez){this.x=x;this.y=y;this.z=
z;}[LastModified("14Feb2010","ClassCreateasofcollections
uupportforvector")]publicIEnumeratorGetEnumerator(){return
null;}[LastModified("14Feb2010","Methodaddedinordertopr
ovideformattingsupport")]publicstringToString(stringformat,
IFormatProviderformatProvider){if(format==null){returnTo
String();}returnnull;}}}反射选介绍System.Type类,通过这个类可以访问关于任何数据类型的
信息。然后介绍System.Reflection.Assembly类,它可以用于访问给定程序集的相关信息。或都把这个程序集加载到程
序中。最后把本节代码和上一节联系起来,完成WhatsNewAttributes示例。System.Type类Typetype=
typeof(double);这里使用Type只为了存储类型的引用。我们以前把Type看作是一个类,但它实际是一个抽象的基类。只
要实例化了一个Type对象,实际上就实例化了Type的一个派生类。通常取指向任何给定类型的Tpye的引用有3种方式:1.Type
type=typeof(double);2.使用GetType方法,所有的类都会从System.Object继承这个方法Ty
pet=140.GetType();还可以调用Type类的静态方法GetType();TypemyType=Type.
GetType("Double");Type是许多反射功能的入口。它实现了许多方法和属性,这里不可能列出所有的方法和属性,而主要介
绍如何使用这个类。注意,可用的属性都是只读的:可以使用Type确定数据的类型,但不能使用它修改改类型!Type属性由Type实现的
属性可以分为下述3个类。首先,许多属性都可以获取包含与类相关的各种名称的字符串:属性返回值Name数据类型名FullName包括名
称空间的数据类型名Namespace数据类型的名称空间其次,属性还可以进一步获得Type对象的引用,这些引用表示相关的类。属性返回
对应的Type引用BaseTypeType直接基类的引用UnderlyingSystemType指示表示该类型的公共语言运行时提供
的类型。还有许多布尔属性:IsAbsract、IsArray、IsClass、IsEnum、IsInterface、IsPoint
er,IsPublic,IsSealed,IsValueTypeTypetype=typeof(double);Conso
le.WriteLine(type.IsPrimitive);//TrueConsole.WriteLine(type.IsAb
stract);//FalseConsole.WriteLine(type.IsClass);//FalseConsole.
WriteLine(type.IsEnum);//FalseConsole.WriteLine(type.IsValueTyp
e);//True方法Type的大多数方法都用于获取对应数据类型的成员信息:构造函数、属性、方法、和事件等。它有许多方法,但它们
都有相同的模式。例如,有两个方法可以获取数据类型的方法的细节信息:GetMethod()和GetMethods().GeMetho
d返回System.Reflection.MethodInfo对象的一个引用,其中包含一个方法的细节信息。GetMethods()
返回这种引用的一个数组(所有方法的细节)。这两个方法都有重载,重载方法有一个附加的参数,邓BindingFlags枚举值,表示返
回哪些成员,例如返回公有成员,实例成员静态成员等:staticvoidMain(string[]args){Type
type=typeof(double);Console.WriteLine(type.IsPrimitive);//True
Console.WriteLine(type.IsAbstract);//FalseConsole.WriteLine(typ
e.IsClass);//FalseConsole.WriteLine(type.IsEnum);//FalseConso
le.WriteLine(type.IsValueType);//Trueforeach(varitemintype.G
etMethods()){Console.Write(item.ReturnType.Name+""+item.N
ame+"(");foreach(varparinitem.GetParameters()){Console.Wri
te(par.ParameterType+""+par.Name);}Console.WriteLine(")");
}}返回的对象类型方法ConstructorInfoGetConstructor(),GetConstructors(),Ev
entInfoGetEvent(),GetEvents()FieldInfoGetField(),GetFields()Membe
rInfoGetMember(),GetMemebrs(),GetDefaultMembers()MethodInfoGetMet
hod(),GetMethods()PropertyInfoGetProperty(),GetProperties()GetMem
ber()和GetMembers()返回数据类型的任成员或所有成员的详细信息,不管这成员是构造函数,属性或方法等。TypeView
示例usingSystem;usingSystem.Collections.Generic;usingSystem.Linq
;usingSystem.Reflection;usingSystem.Text;namespace反射{classPr
ogram{staticStringBuilderOutputText=newStringBuilder();st
aticvoidMain(string[]args){Typet=typeof(double);AnalyzeT
ype(t);Console.WriteLine(OutputText.ToString());}privatestati
cvoidAnalyzeType(Typet){AddToOutput("TypeName:"+t.Name);Ad
dToOutput("FullName:"+t.FullName);AddToOutput("Namespace:"+t
.Namespace);TypetBase=t.BaseType;if(tBase!=null){AddToOu
tput("BaseType"+tBase.Name);}TypetUnderlyingSystem=t.Under
lyingSystemType;if(tUnderlyingSystem!=null){AddToOutput("Un
derlyingSystem:"+tUnderlyingSystem.Name);}MemberInfo[]Member
s=t.GetMembers();foreach(variteminMembers){AddToOutput(i
tem.DeclaringType+""+item.MemberType+""+item.Name);}}
privatestaticvoidAddToOutput(stringv){OutputText.Append("\
n"+v);}}}Assembly类Assembly类在System.Reflection名称空间中定义,它允许访问给定
程序集的元数据,它也包含可以加载和执行程序集的方法。与Type类一样,Assembly类包含非常多的方法和属性,这里不可能逐一论述
。下面仅完成WhatsNewAttributes示例所需要的方法和属性。在使用Assembly实例做一些工作前,需要把相应的程序集
加载到正在运行的进程中。为此,可以使用静态成员Assembly.Load或AssemblyLoadFrom().这两个方法的区别是
Load()方法的参数是程序集的名称,运行库会在各个位置上搜索该程序集,试图找到这个程序集,这些为置包括本地目录和全局程序集缓存。
而LoadFrom方法的参数是程序集的完整路径名,它会在其它位置搜索这个程序集:staticvoidMain(string[]
args){//stringassemblyName="WahtsNewAttributes.dll";strin
gassemblyPath=@"E:\luan\vs\C#\C#高级编程\反射\VectorClass\bin\Debug\
VectorClass.dll";//Assemblyassembly1=Assembly.Load(assemblyPa
th);Assemblyassembly2=Assembly.LoadFrom(assemblyPath);Consol
e.WriteLine(assembly2.FullName);Console.ReadKey();}获取在程序集中定义的类型
的详细信息staticvoidMain(string[]args){//stringassemblyName="
WahtsNewAttributes.dll";stringassemblyPath=@"E:\luan\vs\C#\C#
高级编程\反射\VectorClass\bin\Debug\VectorClass.dll";//Assemblyassemb
ly1=Assembly.Load(assemblyPath);Assemblyassembly2=Assembly.
LoadFrom(assemblyPath);Console.WriteLine(assembly2.FullName);Ty
pe[]types=assembly2.GetTypes();foreach(varitemintypes){
Console.WriteLine(item.FullName);}Console.ReadKey();}获取自定义特性的详
细信息用于查找在程序集或类型中定义了什么自定义特性的方法取决于与该特性相关的对象类型。如果要确定程序集从整体上关联了什么自定义特性
,就需要调用Attribute类的一个静态方法。GetCustomAttributes();Attribute[]attribu
tes=Attribute.GetCustomAttributes(assembly2);Console.WriteLine
("#############################################");foreach(vari
teminattributes){Console.WriteLine(item);}GetCustomAttribut
es(Assembly,Type)检索应用于程序集的自定义属性的数组。参数指定程序集、要搜索的自定义属性的类型以及忽略的搜索选
项。所有特性都作为一般的Attribute引用来获取。如果要调用为自定义任何方法或属性,就需要把这些引用显示转换为相关的自定义特性
类。调用另一个重载方法,可以获得与给定数据类型相关的特性类型,这次传递的是一个Type引用,它描述了要获取的任何相关特性的类型。A
ttributeattribute=Attribute.GetCustomAttribute(assembly2,typeo
f(SupportsWhatsNewAttribute));完成WhatsNewAttributesusingSystem;us
ingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Ref
lection;usingSystem.Text;usingWahtsNewAttributes;namespaceLook
UpWhatsNew{classWhatsNewChecker{privatestaticreadonlyStrin
gBuilderoutputText=newStringBuilder(1000);privatestaticrea
donlyDateTimebackDateTO=newDateTime(2009,2,1);staticvoidM
ain(string[]args){stringassemblyPath=@"E:\luan\vs\C#\C#高级编程
\反射\VectorClass\bin\Debug\VectorClass.dll";AssemblytheAssembly
=Assembly.LoadFrom(assemblyPath);AttributesupportsAttribute=
Attribute.GetCustomAttribute(theAssembly,typeof(SupportsWhatsNewA
ttribute));stringname=theAssembly.FullName;AddToMessage("Ass
embly:"+name);if(supportsAttribute==null){AddToMessage("T
hisassemblydoesnotsupportWhatsNewAttributes");return;}el
se{AddToMessage("DefinedTypes:");}Type[]types=theAssembly
.GetTypes();foreach(varitemintypes){DispalyTypeInfo(item);
}Console.WriteLine(outputText.ToString());}privatestaticvoi
dDispalyTypeInfo(Typeitem){//Makesureweonlypickoutclass
esif(!(item.IsClass)){return;}AddToMessage("\nclass"+ite
m.Name);//传入具体类型的引用Attribute[]attributes=Attribute.GetCusto
mAttributes(item);if(attributes.LongLength==0){AddToMessage(
"Nochangestothisclassn");}else{//这是类型的特性foreach(varattr
ibinattributes){WriteAttributeInfo(attrib);}}MethodInfo[]
methods=item.GetMethods();foreach(varmethodinmethods){ob
ject[]attribs2=method.GetCustomAttributes(typeof(LastModifiedAttribute),false);if(attribs2!=null){AddToMessage(method.ReturnType+""+method.Name+"()");foreach(AttributemethodAttribinattribs2){//这是方法的特性WriteAttributeInfo(methodAttrib);}}}}privatestaticvoidWriteAttributeInfo(Attributeattrib){//显示转换成具体的自定义特性类型LastModifiedAttributelastAttribute=attribasLastModifiedAttribute;if(lastAttribute==null){return;}//checkthatdateisinrangeDateTimemodifiedDate=lastAttribute.DateModified;if(modifiedDate
献花(0)
+1
(本文系luan_it首藏)