Metadata and Attributes

I l @ ve RuBoard

In Chapter 2, you saw how classes are compiled into assemblies. Alongside the compiled (MSIL) version of the application code, assemblies contain additional information about each type they contain ”the name , together with methods , properties, events, and other items. This is metadata .

Note

Do not confuse metadata with manifests . A manifest contains information about the contents of the assembly and about other assemblies that the contents reference. An assembly contains a single manifest. Metadata holds information about a single type in an assembly, and each type has its own set of metadata.


The ILDASM utility uses the metadata of each type in an assembly to present the contents of that assembly in a tree structure. The .NET Framework supplies the System.Reflection namespace, which allows you to write your own code to examine the metadata of a type. If you're familiar with Java, you know that it provides the package java.lang.reflect, which also contains classes and methods for examining metadata. Much of this package has been implemented with J#, and you can also use it for examining types implemented in languages other than J#. However, as with many features in J#, this package was created to allow you to port existing Java code to J# with the minimum of fuss, and you should use the System.Reflection namespace when you build new applications.

Reflection in .NET

Reflection is used throughout .NET. In addition to allowing you to write programs that analyze other types and classes, it has many uses. It is required by languages that use late binding (most of the interpreted scripting languages), for generating wrappers when interoperating with COM, for processing attributes, and for serializing objects when using .NET Remoting.

The System.Type Object and the System.Reflection Namespace

The System.Reflection namespace has a very regular structure, making it easy to understand. (The JDK has a similar set of methods in java.lang.reflect .) Essentially, you extract information about a type using the GetType method. This method returns a System.Type object. The Type class exposes a multitude of properties that allow you to determine information such as whether the type is public ( get_IsPublic ) or private ( get_IsPrivate ), is a class ( get_IsClass ), or is an interface ( get_IsInterface ), as well as retrieve useful information such as the name of the type ( get_Name ) and the type that the current class inherits from ( get_BaseType ). The Type class also exposes methods that allow you to obtain arrays of methods ( GetMethods ), constructors ( GetConstructors ), and fields ( GetFields ), as well as non-Java items such as events ( GetEvents ) and properties ( GetProperties ) if the type was created in a language other than J#. You can search for specific methods, constructors, and fields using the GetMethod (singular), GetConstructor , and GetField methods, respectively. For more information, look up the Type class in the System namespace in the .NET Framework Class Library documentation that ships with Visual Studio .NET.

The various GetXXXs methods return arrays of XXXInfo objects. For example, GetMethods returns an array of MethodInfo objects. Like the Type class, the XXXInfo classes have properties that you can query to determine information such as the accessibility of a class member (private, public, protected, and so on), whether the member is static, virtual, abstract, final, and so on. In addition to these common properties that all members have (these methods are defined in the MethodBase class that the various XXXInfo classes inherit from), each XXXInfo class exposes additional methods and properties peculiar to the member type. For example, MethodInfo has a get_ReturnType property that indicates the type returned by the method, as well as an Invoke method that allows you to call the corresponding method dynamically. (This is very useful when you perform late binding). All the XXXInfo classes are defined in the System.Reflection namespace.

Using Reflection

Perhaps the best way to understand the Type class and the System.Reflection namespace is to see an example in action. The ReflectionDemo program contains a class (also called ReflectionDemo ) that prompts the user for the name of an assembly ”either a DLL or an EXE ”and then uses reflection to display the metadata for every type in that assembly. The program dynamically loads the assembly using the static method Assembly.LoadFrom , as shown here:

 Assemblyassembly=Assembly.LoadFrom(assemblyName); 

The Assembly class provides a useful mechanism for querying, loading, and manipulating assemblies; you'll find it in the System.Reflection namespace. The program then calls the GetTypes method of the newly loaded assembly, which returns an array of all the types defined in the assembly:

 Type[]typeList=assembly.GetTypes(); 

The program iterates through each type in the array, determining the class that the type inherits from by invoking get_BaseType . This approach works because all Java and J# classes inherit from something, even if it is just the generic Object class. The program also obtains a list of interfaces (if any) that are being implemented by the type by calling the GetInterfaces method. The name of each interface is printed. Here's the code:

 Typet=typeList[i]; //Determinethedirectancestor TypesuperT=t.get_BaseType(); Console.Write("class " +t.get_Name()); if((superT!=null)&&(superT.get_IsClass())) Console.Write(" extends " +superT.get_FullName()); //Doesthisclassimplementanyinterfaces? Type[]interfaceList=t.GetInterfaces(); for(intj=0;j<interfaceList.length;j++) { if(j==0) Console.Write(" implements "); else Console.Write(", "); Console.Write(interfaceList[j].get_Name()); } 

The program continues by displaying the details of all constructors (using the printConstructors method), methods ( printMethods ), and fields ( printFields ) for each type in the assembly. The printConstructors method calls GetConstructors to retrieve a list of the constructors for the current type being examined:

 ConstructorInfo[]constructors=t.GetConstructors(flags); 

The flags parameter specifies which constructors will be found. By default, only public constructors are returned. If you look at the private fields at the start of the class, you'll see that the flags variable is a bitwise combination that specifies public and nonpublic (private and protected) static and instance members. The flags variable itself is actually a BindingFlags enumeration:

 privatestaticBindingFlagsflags=(BindingFlags) (BindingFlags.PublicBindingFlags.NonPublic BindingFlags.InstanceBindingFlags.Static); 

GetConstructors returns an array of ConstructorInfo objects. The printConstructors method investigates each constructor in turn ; accessibility is determined and displayed by examining the get_IsPublic , get_IsPrivate , and get_IsFamily properties:

 //Displaymodifierinformationandthenameoftheconstructor ConstructorInfoc=constructors[i]; System.Stringname=c.get_Name(); if(c.get_IsPublic()) Console.Write("public"); if(c.get_IsPrivate()) Console.Write("private"); if(c.get_IsFamily()) Console.Write("protected"); Console.Write(" " +name+ "("); 

The GetParameters method is used to extract the parameters as a ParameterInfo array. The type and name of each parameter is also displayed. The code looks like this:

 //Displayinformationabouteachparameter ParameterInfo[]params=c.GetParameters(); for(intj=0;j<params.length;j++) { if(j>0) Console.Write(", "); Console.Write(params[j].get_ParameterType().get_Name()+ " " + params[j].get_Name()); } 

The printMethods method is similar. The major difference is that it calls GetMethods to find all the methods for the specified type:

 MethodInfo[]methods=t.GetMethods(flags); 

The list of methods is returned as a MethodInfo array. As each method is processed , the printMethods determines its accessibility and return type and displays them. Another small difference is that printMethods also looks at the get_IsStatic property to determine whether the method is a static method or an instance method. (This does not apply to constructors because they cannot be static.)

 if(m.get_IsStatic()) Console.Write(" static"); 

The printMethods method then outputs the names and types of each parameter using the same technique as printConstructors .

Finally, the printFields method calls GetFields to return an array of FieldInfo objects. The type of the field is extracted using the get_FieldType method of the field:

 Typetype=f.get_FieldType(); 

The modifiers, type, and name of each field are obtained and displayed.

ReflectionDemo.jsl
 importSystem.*; importSystem.Reflection.*; publicclassReflectionDemo { privatestaticBindingFlagsflags= (BindingFlags)(BindingFlags.PublicBindingFlags.NonPublic BindingFlags.InstanceBindingFlags.Static); publicstaticvoidmain(String[]args) { StringassemblyName; Console.Write("Pleaseenteranassemblyname: "); assemblyName=Console.ReadLine(); try { //Loadtheassembly Assemblyassembly=Assembly.LoadFrom(assemblyName); //Findallthetypesintheassembly Type[]typeList=assembly.GetTypes(); //Iteratethrougheachtypeandprintitsdetailsusing //themetadataintheassembly for(inti=0;i<typeList.length;i++) { Typet=typeList[i]; //Determinethedirectancestor TypesuperT=t.get_BaseType(); Console.Write("class " +t.get_Name()); if((superT!=null)&&(superT.get_IsClass())) Console.Write(" extends " +superT.get_FullName()); //Doesthisclassimplementanyinterfaces? Type[]interfaceList=t.GetInterfaces(); for(intj=0;j<interfaceList.length;j++) { if(j==0) Console.Write(" implements "); else Console.Write(", "); Console.Write(interfaceList[j].get_Name()); } //Printthedetailsforthetype Console.WriteLine("\n{"); printConstructors(t); Console.WriteLine(); printMethods(t); Console.WriteLine(); printFields(t); Console.WriteLine("}\n"); } } catch(System.Exceptione) { Console.WriteLine("Exception: " +e); } } //Displayinformationaboutconstructors publicstaticvoidprintConstructors(Typet) { //Findallconstructorsforthetype ConstructorInfo[]constructors=t.GetConstructors(flags); for(inti=0;i<constructors.length;i++) { //Displaymodifierinformationandthenameoftheconstructor ConstructorInfoc=constructors[i]; Stringname=c.get_Name(); if(c.get_IsPublic()) Console.Write("public"); if(c.get_IsPrivate()) Console.Write("private"); if(c.get_IsFamily()) Console.Write("protected"); Console.Write(" " +name+ "("); //Displayinformationabouteachparameter ParameterInfo[]params=c.GetParameters(); for(intj=0;j<params.length;j++) { if(j>0) Console.Write(", "); Console.Write(params[j].get_ParameterType().get_Name()+  " " +params[j].get_Name()); } Console.WriteLine(");"); } } //Displayinformationaboutmethods publicstaticvoidprintMethods(Typet) { //Findallmethodsforthetype MethodInfo[]methods=t.GetMethods(flags); for(inti=0;i<methods.length;i++) { //Displaythemodifiers,returntype,andnameofthemethod MethodInfom=methods[i]; Stringname=m.get_Name(); if(m.get_IsPublic()) Console.Write("public"); if(m.get_IsPrivate()) Console.Write("private"); if(m.get_IsFamily()) Console.Write("protected"); if(m.get_IsStatic()) Console.Write(" static"); TyperetType=m.get_ReturnType(); Console.Write(" " +retType.get_Name()+ " " +name+ "("); //Displaythetypeandnameofeachparameter ParameterInfo[]params=m.GetParameters(); for(intj=0;j<params.length;j++) { if(j>0) Console.Write(", "); Console.Write(params[j].get_ParameterType().get_Name()+  " " +params[j].get_Name()); } Console.WriteLine(");"); } } //Displayinformationaboutfields publicstaticvoidprintFields(Typet) { //Findallfieldsforthetype FieldInfo[]fields=t.GetFields(flags); for(inti=0;i<fields.length;i++) { //Displaythemodifier,type,andnameofeachfield FieldInfof=fields[i]; Typetype=f.get_FieldType(); Stringname=f.get_Name(); if(f.get_IsPublic()) Console.Write("public"); if(f.get_IsPrivate()) Console.Write("private"); if(f.get_IsFamily()) Console.Write("protected"); if(f.get_IsStatic()) Console.Write(" static"); Console.WriteLine(" " +type.get_Name()+ " " +name+ ";"); } } } 

You can run this program over any of the .NET-supplied assemblies or those you've written yourself. It is actually quite instructive to use it to examine the ReflectionDemo.exe assembly, as you can see in Figure 3-8.

Figure 3-8. ReflectionDemo running over the ReflectionDemo.exe assembly

You'll notice that a certain amount of mapping from Java to .NET types occurs. In addition, there are two constructors, .ctor and .cctor . The .ctor constructor is the default instance constructor. In Java, if you don't define any constructors, a default constructor is created for you automatically. The .cctor constructor is the type constructor . Its purpose is to initialize the static fields in the class, and it will also be created automatically if a class contains static data.

Attributes

We talked about attributes in Chapter 2. An attribute is a piece of declarative information that is stored with the metadata of a type. At run time, this metadata can be examined and used to modify the way in which the type operates. The .NET Framework defines a large number of attributes. Most cause annotations to be inserted into the compiled code and are interpreted by the common language runtime. The examples in Chapter 2 included PrincipalPermissionAttribute , which the common language runtime uses to determine whether a user can execute a particular method. The use of this attribute is shown in the following code:

 publicclassCakeInfo { /**@attributePrincipalPermissionAttribute(SecurityAction.Demand, Role="WONDERLAND\Bakers") */ /**@attributePrincipalPermissionAttribute(SecurityAction.Demand, Name="WONDERLAND\JSharp") */ publicstaticshortFeedsHowMany(shortdiameter,shortshape, shortfilling) { } } 

If you use ILDASM to examine the FeedsHowMany method of the CakeInfo class in the CakeUtils assembly, you'll see the effects of these attributes on the code ”a .permissionset block is added to the start of the method, and the common language runtime will examine this information to determine whether to allow the current user to execute the code, as shown in Figure 3-9.

Figure 3-9. ILDASM showing the effects of PrincipalPermissionAttribute

Other attributes are handled in different ways by the compilers and the common language runtime. Depending on the attributes being used, they can be applied to an entire assembly (the AssemblyVersionAttribute , for example), a class, individual methods, constructors, fields, interfaces, and even parameters and return values. You'll see many more examples of attributes later in this book.

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net