Metadata is one of those terms that usually receives a quick confirming nod when explained. At the same time, it is one of those terms that almost always requires an explanation. Basically, metadata is data about data, or even data about objects. In other words, information that might be used to identify or describe something is metadata.
Say that you are coding a mainframe COBOL batch program. When the program is complete, you might also create a JCL Job structure to use for program execution. Then, if I was to come along behind you and browse the PDS library where your JCL Job is saved and open the JCL Job, I would be able to "read" your JCL, right? What would I know at that point? Just from "reading" your JCL, I would be able find out several facts.
The very first JCL statement in every JCL Job describes (at a minimum) the Job name of the JCL Job. Typically, I would also be able to find out the input class and message class values, chargeback /accounting information, execution priority, and whether the "type of run" will be held or not. I could possibly obtain all of that information just from the first JCL statement: the Job statement. If I were to look further into the JCL Job at other JCL statements, I would be able to obtain even more pieces of descriptive information (e.g., which load libraries are referenced, the names of any cataloged procedures being used, and possibly the names of the programs being executed).
That's right. All of the descriptive information, the data about the JCL Job, is metadata. Now, imagine if you were to write a mainframe batch utility program to read the PDS member (the one that actually contains the JCL Job) as text data. Your utility program would be able to take the text line by line, and display/report on the so-called metadata. All right, how about another confirming nod? Thanks. Let's now see how metadata is accessed on the .NET platform.
Fortunately, there is no need to write your own program to do the low-level data mining needed for gathering metadata. .NET provides a namespace specifically designed to gather metadata. Naturally, the namespace, System.Reflection , contains delegates, interfaces, classes, enumerations, and structures. The mainframe JCL analogy had the JCL Job as the focal point for metadata excavation. With .NET, the assembly (.exe or .dll) becomes the target for exploration using the System.Reflection namespace.
Attributes make up a special breed of metadata for your .NET applications. There are two groups of attributes: intrinsically available attributes and custom attributes that you create yourself. Take a look at your assembly manifest sometime (using the ILDASM tool or programmatically via reflection). You will see the metadata provided by attributes. You can use attributes to further document/ describe your application. Attributes also have other uses. For example, you can use them to help manage your production deployments through version control (I further discuss deployment in Chapter 17). That is not all. When you combine the use of attributes with metadata, you can easily choose to add compiler directives to your application. You see, the support for metadata is an important feature in .NET.
Each time you use the ILDASM tool, you get a taste of what "reflection" looks like. Recall that the first page displayed when the ILDASM tool is used simply describes the assembly. It is after drilling down that you get to the actual Microsoft intermediate language (MSIL). To demonstrate this, you will use the two sample code assemblies created earlier in this chapter in the section "Demonstrating How to Access Data in Text Files": SystemIOExampleCobol.exe and SystemIOExampleVB.exe. As shown in Figures 10-1 and 10-2, the first page displayed with the ILDASM tool basically describes each respective assembly.
As you look at each assembly's ILDASM display, are you reminded of the mainframe analogy of looking inside a JCL Job? Good. Now you will take a look at some sample code to understand the programmatic access of metadata.
I have created two sample projects: MetaDataExampleVB and MetaDataExampleCobol. You will see that each project programmatically accesses the metadata for the System.Text.StringBuilder class that is contained in the Microsoft assembly mscorlib.dll.
To help prepare for the demonstration code, please review the ILDASM display shown in Figure 10-3. You will notice that even the Microsoft assembly mscorlib.dll is not immune to this type of metadata observation. The ILDASM tool uses reflection to display the types contained in the mscorlib.dll assembly. The StringBuilder class is one of the types.
The code in Listing 10-8 demonstrates the use of two System.Reflection classes: System.Reflection.MemberInfo and System.Reflection.Assembly . The code was copied from the MetaDataExampleVB.exe sample project.
Module Module1 Sub Main() 'Create Objects from System.Reflection Namespace Dim myMemberInfoArray() As System.Reflection.MemberInfo Dim myAssembly As System.Reflection.Assembly Dim MyTypes() As System.Type Dim MyType As System.Type Dim indexA As Integer Dim indexB As Integer 'Create Object from StringBuilder Class Dim sb As New System.Text.StringBuilder() 'Get the Type associated with the StringBuilder MyType = sb.GetType 'Get the Assembly associated with the StringBuilder Type myAssembly = myAssembly.GetAssembly(MyType) 'Get the Types found in the Assembly MyTypes = myAssembly.GetTypes() For indexA = 0 To UBound(MyTypes) 'Select specific Type for further processing If MyTypes(indexA).Name = "StringBuilder" Then 'Display appropriate Information Console.WriteLine(MyTypes(indexA).FullName) 'Get members found in the selected Type myMemberInfoArray = MyTypes(indexA).GetMembers() For indexB = 0 To myMemberInfoArray.Length - 1 'Display appropriate Information sb.Length = 0 With sb .Append("MemberType - ") .Append(" ") .Append(myMemberInfoArray(indexB).MemberType.ToString()) .Append(" ") .Append("Name -") .Append(" ") .Append(myMemberInfoArray(indexB).ToString()) .Append(" ") Console.WriteLine(.ToString()) End With Next indexB 'Exit Loop after StringBuilder Type is located Exit For End If Next Console.ReadLine() End Sub End Module
With a little practice, using the reflection namespaces is not very difficult. The sample code in Listing 10-9 demonstrates exactly how to go about doing so. Please take a moment to read the code. I will assume that you are continuing to approach your .NET retraining with a bilingual flavor. With that understanding, I am sure you will find the comparisons between the two sample projects (MetaDataExampleVB.exe in Listing 10-8 and MetaDataExampleCobol.exe in Listing 10-9) rather interesting and informative.
000010 IDENTIFICATION DIVISION. 000020* This is an example of how to use the 000030* System.Reflection Classes to obtain MetaData 000040 PROGRAM-ID. MAIN. 000050 ENVIRONMENT DIVISION. 000060 CONFIGURATION SECTION. 000070 REPOSITORY. 000080* .NET Framework Classes 000090 CLASS SYS-sb AS "System.Text.StringBuilder" 000100 CLASS SYS-MyString As "System.String" 000110 CLASS SYS-MyInt As "System.Int32" 000120 CLASS SYS-MyType As "System.Type" 000130 CLASS SYS-MyTypes As "System.Type[]" 000140 CLASS SYS-myMemberInfoItem As "System.Reflection.MemberInfo" 000150 ENUM ENUM-myMemberTypes As "System.Reflection.MemberTypes" 000160 CLASS SYS-myMemberInfoArray As "System.Reflection.MemberInfo[]" 000170 CLASS SYS-myAssembly As "System.Reflection.Assembly" 000180 PROPERTY PROP-Length as "Length" 000190 PROPERTY PROP-FullName as "FullName" 000200 PROPERTY PROP-Name as "Name" 000210 PROPERTY PROP-MemberType as "MemberType". 000230* 000240 DATA DIVISION. 000250 WORKING-STORAGE SECTION. 000260 77 sb OBJECT REFERENCE SYS-sb. 000270 77 MyString OBJECT REFERENCE SYS-MyString. 000280 77 MyInt OBJECT REFERENCE SYS-MyInt. 000290 77 MyType OBJECT REFERENCE SYS-MyType. 000300 77 MyTypes OBJECT REFERENCE SYS-MyTypes. 000310 77 MyTypeItem OBJECT REFERENCE SYS-MyType. 000320 000330* Reference Objects from System.Reflection Namespace 000340 77 myMemberInfoItem OBJECT REFERENCE SYS-myMemberInfoItem. 000350 77 myMemberInfoArray OBJECT REFERENCE SYS-myMemberInfoArray. 000360 77 myAssembly OBJECT REFERENCE SYS-myAssembly. 000370 77 myMemberTypes OBJECT REFERENCE ENUM-myMemberTypes. 000380 000390 77 indexA PIC S9(9) COMP-5. 000400 77 indexB PIC S9(9) COMP-5. 000410 77 ArrayBoundaryA PIC S9(9) COMP-5. 000420 77 ArrayBoundaryB PIC S9(9) COMP-5. 000430 77 MyDisplayString PIC X(100). 000440 01 NULL-X PIC X(1). 000450 LINKAGE SECTION. 000460 000470 PROCEDURE DIVISION. 000480 000490* Create Object from StringBuilder Class 000500 SET sb to SYS-sb::"NEW" () 000510* Get the Type associated with the StringBuilder 000520 SET MyType TO sb::"GetType" () 000530* Get the Assembly associated with the StringBuilder Type 000540 SET myAssembly TO SYS-myAssembly::"GetAssembly" (MyType) 000550* Get the Types found in the Assembly 000560 SET MyTypes TO myAssembly::"GetTypes" () 000570 000580 SET MyInt to MyTypes::"GetUpperBound" (0) 000590 SET ArrayBoundaryA to MyInt 000600 PERFORM VARYING indexA 000610 FROM 0 BY 1 UNTIL indexA >= ArrayBoundaryA 000620* Select specific Type for further processing 000630 INVOKE MyTypes "Get" USING BY VALUE indexA RETURNING MyTypeItem 000640* Display appropriate Information 000650 SET MyDisplayString to PROP-Name of MyTypeItem 000660 IF MyDisplayString = "StringBuilder" Then 000670 SET MyDisplayString to PROP-FullName of MyTypeItem 000680 Display MyDisplayString 000690* Get members found in the selected Type 000700 SET myMemberInfoArray to MyTypeItem::"GetMembers" () 000710 SET MyInt to PROP-Length of myMemberInfoArray 000720 SET ArrayBoundaryB to MyInt 000730 PERFORM VARYING indexB 000740 FROM 0 BY 1 UNTIL indexB >= (ArrayBoundaryB - 1) 000750 INVOKE myMemberInfoArray "Get" 000760 USING BY VALUE indexB RETURNING myMemberInfoItem 000770 000780 SET myMemberTypes to PROP-MemberType of myMemberInfoItem 000790 SET MyString to myMemberInfoItem::"ToString" () 000800 SET PROP-Length of sb to 0 000810* Display appropriate Information 000820 INVOKE sb "Append" USING BY VALUE "MemberType - " 000830 RETURNING sb 000840 INVOKE sb "Append" USING BY VALUE " " 000850 RETURNING sb 000860 INVOKE sb "Append" USING BY VALUE myMemberTypes 000870 RETURNING sb 000880 INVOKE sb "Append" USING BY VALUE " " 000890 RETURNING sb 000900 INVOKE sb "Append" USING BY VALUE "Name -" 000910 RETURNING sb 000920 INVOKE sb "Append" USING BY VALUE " " 000930 RETURNING sb 000940 INVOKE sb "Append" USING BY VALUE MyString 000950 RETURNING sb 000960 INVOKE sb "Append" USING BY VALUE " " 000970 RETURNING sb 000980 SET MyDisplayString to sb::"ToString" () 000990 DISPLAY MyDisplayString 001000 END-PERFORM 001010* Exit Perform after StringBuilder Type is located 001020 EXIT PERFORM 001030 End-IF 001040 END-PERFORM 001050 DISPLAY "Enter X and Press Enter to Exit.". 001060 ACCEPT NULL-X. 001070 END PROGRAM MAIN.
Why speak about .NET's support for metadata? The answer to this question is different for each person. For myself , I see this as a great opportunity to easily document an assembly. Basically, the assembly ends up documenting itself. You can capture metadata and write the information to a text file (using System.IO.TextWriter). Then, you can archive the text file for application documentation.
For a better idea of the "documentation" that is automatically available, run either sample project (MetaDataExampleVB.exe or MetaDataExampleCobol.exe). Your console display will reveal some of the metadata that is available for this mscorlib.dll assembly, specifically for the StringBuilder type. I have included a small portion of that display in Listing 10-10.
System.Text.StringBuilder MemberType - Method Name - Int32 GetHashCode() MemberType - Method Name - Boolean Equals(System.Object) MemberType - Method Name - System.String ToString() MemberType - Method Name - Int32 get_Capacity() MemberType - Method Name - Void set_Capacity(Int32) MemberType - Method Name - Int32 get_MaxCapacity() MemberType - Method Name - Int32 EnsureCapacity(Int32) . . . MemberType - Method Name - System.Type GetType() MemberType - Constructor Name - Void .ctor() MemberType - Constructor Name - Void .ctor(Int32) MemberType - Constructor Name - Void .ctor(System.String) MemberType - Constructor Name - Void .ctor(System.String, Int32) . . . MemberType - Property Name - Int32 Capacity MemberType - Property Name - Int32 MaxCapacity MemberType - Property Name - Int32 Length MemberType - Property Name - Char Chars [Int32]
As you can see, with metadata and .NET's reflection technology, an application moves closer to being " self-documenting ." I see that as being a great thing with great potential. Learn to leverage your metadata.