Accessing the Metadata of a Component: A Brief Introduction


We form expressions by combining many different kinds of operands (constants, instance variables, method calls, and so on) from different origins. Sometimes, an operand belongs to the current object; sometimes it is fetched from another object. Irrespective of its origin, each operand's type must be carefully checked to ensure that the final expression returns a meaningful result. Finding the type of an operand has been easy in our examples. With all classes written in a small confined world of familiar C# source code, we have simply found it by looking at the method header or the variable declaration in the relevant class definition. We have then deciphered the meaning of recognizable type keywords, like int and double, and thereby revealed the attributes (range and so on) of the operand in question.

But what if our source code became a component traveling in the outside diverse world, for use in contexts where int and double are meaningless words, but where the ability to determine the attributes of a type still are important? Perhaps our component would provide data to a database or exchange data with other components written in other computer languages with different syntax and different configuration, or perhaps even be part of a Web service sent across the Internet.

If our component could describe its types in a flexible, user-friendly manner and thereby avoid the need for other contexts to decipher its internal rigid hard-coded standards, it would be able to seamlessly integrate with many different environments. C#'s built-in support for metadata allows for this separation of implementation and contextual details.

Chapter 1, "Computers and Computer Programming: Basic Concepts" and Chapter 2, "Your First C# Program" introduced the metadata concept and discussed the compiler's ability to automatically emit metadata relevant to an assembly (program). Metadata contains detailed descriptions of the methods, instance variables, and many other important characteristics of each type in a program. One of the attributes provided is simply the name of the type, such as System.Int32 or System.Decimal.

The process of accessing metadata during runtime is called reflection. It is not the aim here to provide a thorough discussion of this advanced subject. Instead, we will merely provide an example of how reflection can be used to expose the name of a type. Not only does this allow us to check our findings in Figure 7.6 of the previous section, it also gives you a glimpse of what metadata is from a practical perspective and how it can be accessed.

Our goal here is to determine the type of a few expressions in an actual program through reflection.

Recall the ToString method discussed in Chapter 6. The ToString method is just one of several useful methods belonging to each of the simple types. If you browse through the fields of the simple types in the .NET Framework Reference, you will find another valuable method called GetType.

When we call the GetType method for an expression, it returns an object of class Type that gives us access to all the metadata kept about the type of this expression.



The GetType method is available for any value of C#, whether this value is of a predefined simple type, a value type, or an object of a reference type. Even your own custom-made classes are equipped with this (and several other) methods for free by C#. When I present an important OOP concept called inheritance later, you will see how this is made possible.

The Type class is part of the .NET Framework class library and is located in the System namespace. An object of class Type is packed with methods and properties, such as the few mentioned in Table 7.3, that are ready to answer questions posed about the metadata of a particular type.

Table 7.3. Three Examples of Information Accessible via an Object of Class Type
Property Brief Explanation
FullName Returns the fully qualified name of the type, including the namespace
IsPrimitive Returns true if the type is a simple type; otherwise, false
IsClass Returns true if the type is a class; otherwise, false



FullName, IsPrimitive, and IsClass are not methods. They are properties. A property is an important C# construct not yet discussed. It allows you to access instance variables of an object from outside the object without violating the rules of encapsulation discussed previously. A detailed discussion of properties will be provided in Chapter 14, "Class Anatomy Part III: Writing Intuitive Code." Properties are in many ways similar to methods, so, for now, you can merely regard the three properties of Table 7.3 as being methods. The only difference you need to be aware of is that properties, when called, do not require parentheses after their name as the methods do.

Property names should, according to Microsoft's style guidelines, be written with Pascal casing.

Listing 7.4 illustrates how we can utilize the three properties shown in Table 7.3 to access information about simple types.

Listing 7.4 Source Code for MetadataAccessor.cs
01: using System; 02: /* 03:  * This class demonstrates how the metadata of 04:  * a given type can be accessed. 05:  */ 06: class MetadataAccessor 07: { 08:     public static void Main() 09:     { 10:         Type anyType; 11:         byte age = (byte)37; 12:         short energy = (short)4000; 13:         ushort height = (ushort)190; 14:         decimal mass = 398.98765m; 15: 16:         anyType = age.GetType(); 17:         Console.WriteLine("The type of the age variable is: " 18:             + anyType.FullName); 19:         if(anyType.IsPrimitive) 20:             Console.WriteLine("The age variable is a simple type"); 21:         if(anyType.IsClass  == false) 22:         Console.WriteLine("The age variable is not a class type"); 23:         anyType = 100.GetType(); 24:         Console.WriteLine("The type of the literal 100 is: " + 25:             anyType.FullName); 26:         anyType = 200.45.GetType(); 27:         Console.WriteLine("The type of the literal 200.45 is: " + 28:             anyType.FullName); 29:         anyType = (age * mass).GetType(); 30:         Console.WriteLine("The type of expression (age * mass) is: " + 31:             anyType.FullName); 32:         anyType = (age + height).GetType(); 33:         Console.WriteLine("The type of the expression " + 34:             "(age + height) is: " + anyType.FullName); 35:         anyType = ((age * mass) * (energy + height)).GetType(); 36:         Console.WriteLine("The type of the expression " + 37:             "((age * mass) * (energy + height)) is: " + 38:             anyType.FullName); 39:     } 40: } The type of the age variable is: System.Byte The age variable is a simple type The age variable is not a class type The type of the literal 100 is: System.Int32 The type of the literal 200.45 is: System.Double The type of expression (age * mass) is: System.Decimal The type of the expression (age + height) is: System.Int32 The type of the expression ((age * mass) * (energy + height)) is: System.Decimal 

Line 10 declares anyType to hold a reference to an object of class Type. The anyType variable will reference the object from which we will receive information about the metadata of a particular type.

Line 16 calls the GetType() method of the age variable by using the dot operator. GetType() returns an object of class Type holding the metadata about age's type in this case, byte. The Type object is assigned to the anyType variable. anyType is now ready to provide information about the metadata of the byte type.

Lines 17 and 18 call the FullName property of anyType, which is returned and printed on the console. As expected (see the previous output), the type is System.Byte.

Lines 19 and 20 represent an if statement. The IsPrimitive property (see Table 7.3) is utilized to detect whether byte is a simple (primitive) type. If this is the case, anyType.IsPrimitive will be true, causing the program to print "The age variable is a simple type". As expected, this is what we see in the output.

Lines 21 and 22 utilize the bool value false in the condition of the if statement. If anyType.IsClass returns the bool value false, the condition (anyType.IsClass == false) will be true and the string "The age variable is not a class type" is printed on the console. byte is a value type (a struct) not a class, so anyType.IsClass is false and (anyType.IsClass == false) is true, causing the text to be printed as shown in the previous output.

Line 23 accesses GetType() directly from the literal 100 and assigns the returned object to anyType.

Lines 24 and 25 confirm, through their output, that the type of 100 is System.Int32.

Lines 29 31 demonstrate our ability to access the Type object of the value returned from an expression combining two variables with an operator. In this case, we are confirming that the type of a value returned from an expression combining values of type byte and type decimal is, indeed, of type decimal (System.Decimal).

Line 34 shows that a longer expression can be investigated by the GetType() method. In fact, any expression of any complexity can be scrutinized in this fashion.

Listing 7.4 uses a somewhat longwinded way of accessing the metadata of a value to emphasize the process involved.

Instead of writing

 10:         Type anyType;     ... 16:         anyType = age.GetType(); 17:         Console.WriteLine("The type of the age variable is: " 18:             + anyType.FullName); 

which highlights the idea of a Type object being returned and assigned to anyType, ready to be "questioned," we could discard lines 10 and 16 and simply write

 17:         Console.WriteLine("The type of the age variable is: " 18:             + age.GetType().FullName); 

After age.GetType() has returned the Type object, we can regard age.GetType() as being the Type object. Consequently, it is possible to write age.GetType().FullName as in line 18.

Allowing Different Languages to Collaborate


The FullName property will always return the name of a type in the long .NET form (System.Int32), not the short C# form (int). This is because all programming languages that work on top of .NET can call GetType, Type, and FullName. Just as int is merely an alias for System.Int32, other languages would use other aliases to refer to this same type. For example, the Eiffel# language uses INTEGER as an alias for System.Int32.

Each language that works on top of the .NET platform uses the same simple types provided by the Common Type System of the .NET platform. So, whether we reflect on a component written in Eiffel, Visual Basic, Perl, or any other .NET language, we will only get names and other attributes of types with which we are familiar. This is a very unifying and powerful concept that makes it possible for components and programmers with different language backgrounds to work together on the same projects and on a detailed level.


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: