Chapter 17. Attributes and Reflection

As well as containing code and data, a .NET program can also contain metadata. Metadata is information about the data—that is, information about the types, code, fields, and so on—stored along with your program. This chapter explores how some of that metadata is created and used.

A lot of the metadata is information that .NET needs in order to understand how your code should be used—for example, metadata defines whether a particular method is public or private. But you can also add custom metadata, using attributes.

Reflection is the process by which a program can read its own metadata, or metadata from another program. A program is said to reflect on itself or on another program, extracting metadata from the reflected assembly and using that metadata either to inform the user or to modify the program’s behavior.

Attributes

An attribute is an object that represents data you want to associate with an element in your program. The element to which you attach an attribute is referred to as the target of that attribute. For example, in Chapter 12 we saw the XmlIgnore attribute applied to a property:

[XmlIgnore]
public string LastName { get; set; }

This tells the XML serialization system that we want it to ignore this particular property when converting between XML and objects of this kind. This illustrates an important feature of attributes: they don’t do anything on their own. The XmlIgnore attribute contains no code, nor does it cause anything to happen when the relevant property is read or modified. It only has any effect when we use XML serialization, and the only reason it does anything then is because the XML serialization system goes looking for it.

Attributes are passive. They are essentially just annotations. For them to be useful, something somewhere needs to look for them.

Types of Attributes

Some attributes are supplied as part of the CLR, some by the . NET Framework class libraries, and some by other libraries. In addition, you are free to define custom attributes for your own purposes.

Most programmers will use only the attributes provided by existing libraries, though creating your own custom attributes can be a powerful tool when combined with reflection, as described later in this chapter.

Attribute targets

If you search through the .NET Framework class libraries, you’ll find a great many attributes. Some attributes can be applied to an assembly, others to a class or interface, and some, such as [XmlIgnore], are applied to properties and fields. Most attributes make sense only when applied to certain things—the XmlIgnore attribute cannot usefully be applied to a method, for example, because methods cannot be serialized to XML. So each attribute type declares its attribute targets using the AttributeTargets enumeration. Most of the entries in this enumeration are self-explanatory, but since a few are not entirely obvious, Table 17-1 shows a complete list.

Table 17-1. Possible attribute targets

Member name

Attribute may be applied to

All

Any of the following elements: assembly, class, constructor, delegate, enum, event, field, interface, method, module, parameter, property, return value, or struct

Assembly

An assembly

Class

A class

Constructor

A constructor

Delegate

A delegate

Enum

An enumeration

Event

An event

Field

A field

GenericParameter

A type parameter for a generic class or method

Interface

An interface

Method

A method

Module

A module

Parameter

A parameter of a method

Property

A property (both get and set, if implemented)

ReturnValue

A return value

Struct

A struct

Applying attributes

You apply most attributes to their targets by placing them in square brackets immediately before the target item. A couple of the target types don’t correspond directly to any single source code feature, and so these are handled differently. For example, an assembly is a single compiled .NET executable or library—it’s everything in a single project—so there’s no one feature in the source code to which to apply the attribute. Therefore, you can apply assembly attributes at the top of any file. The module attribute target type works the same way.[48]

Note

You must place assembly or module attributes after all using directives and before any code.

You can apply multiple attributes, one after another:

[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile(".\keyFile.snk")]

Alternatively, you can put them all inside a single pair of square brackets, separating the attributes with commas:

[assembly: AssemblyDelaySign(false),
 assembly: AssemblyKeyFile(".\keyFile.snk")]

The System.Reflection namespace offers a number of attributes, including attributes for assemblies (such as the AssemblyKeyFileAttribute), for configuration, and for version attributes. Some of these are recognized by the compiler—the key file attribute gets used if the compiler generates a digital signature for your component, for example.

Custom Attributes

You are free to create your own custom attributes and use them at runtime as you see fit. Suppose, for example, that your development organization wants to keep track of bug fixes. You already keep a database of all your bugs, but you’d like to tie your bug reports to specific fixes in the code.

You might add comments to your code along the lines of:

// Bug 323 fixed by Jesse Liberty 1/1/2010.

This would make it easy to see in your source code, but since comments get stripped out at compile time this information won’t make it into the compiled code. If we wanted to change that, we could use a custom attribute. We would replace the comment with something like this:

[BugFixAttribute(323, "Jesse Liberty", "1/1/2010",
                 Comment="Off by one error")]

You could then write a program to read through the metadata to find these bug-fix annotations, and perhaps it might go on to update a bug database. The attribute would serve the purposes of a comment, but would also allow you to retrieve the information programmatically through tools you’d create.

Note

This may be a somewhat artificial example, however, because you might not really want this information to be compiled into the shipping code.

Defining a custom attribute

Attributes, like most things in C#, are embodied in classes. To create a custom attribute, derive a class from System.Attribute:

public class BugFixAttribute : System.Attribute

You need to tell the compiler which kinds of elements this attribute can be used with (the attribute target). We specify this with (what else?) an attribute:

[AttributeUsage(AttributeTargets.Class |
    AttributeTargets.Constructor |
    AttributeTargets.Field |
    AttributeTargets.Method |
    AttributeTargets.Property,
    AllowMultiple = true)]

AttributeUsage is an attribute applied to an attribute class. It provides data about the metadata: a meta-attribute, if you will.

We have provided the AttributeUsage attribute constructor with two arguments. The first is a set of flags that indicate the target—in this case, the class and its constructor, fields, methods, and properties. The second argument is a flag that indicates whether a given element might receive more than one such attribute. In this example, AllowMultiple is set to true, indicating that class members can have more than one BugFixAttribute assigned.

Naming an attribute

The new custom attribute in this example is named BugFixAttribute. The convention is to append the word Attribute to your attribute name. The compiler recognizes this convention, by allowing you to use a shorter version of the name when you apply the attribute. Thus, you can write:

[BugFix(123, "Jesse Liberty", "01/01/08", Comment="Off by one")]

The compiler will first look for an attribute class named BugFix and, if it doesn’t find that, will then look for BugFixAttribute.

Constructing an attribute

Although attributes have constructors, the syntax we use when applying an attribute is not quite the same as that for a normal constructor. We can provide two types of argument: positional and named. In the BugFix example, the programmer’s name, the bug ID, and the date are positional parameters, and comment is a named parameter. Positional parameters are passed in through the constructor, and must be passed in the order declared in the constructor:

public BugFixAttribute(int bugID, string programmer,
string date)
{
    this.BugID = bugID;
    this.Programmer = programmer;
    this.Date = date;
}

Named parameters are implemented as fields or as properties:

public string Comment    { get; set; }

Note

You may be wondering why attributes use a different syntax for named arguments than we use in normal method and constructor invocation, where named arguments take the form Comment: "Off by one", using a colon instead of an equals sign. The inconsistency is for historical reasons. Attributes have always supported positional and named arguments, but methods and normal constructor calls only got them in C# 4.0. The mechanisms work quite differently—the C# 4.0 named argument syntax is mainly there to support optional arguments, and it only deals with real method arguments, whereas with attributes, named arguments are not arguments at all—they are really properties in disguise.

It is common to create read-only properties for the positional parameters:

public int BugID    { get; private set; }

Using an attribute

Once you have defined an attribute, you can put it to work by placing it immediately before its target. To test the BugFixAttribute of the preceding example, the following program creates a simple class named MyMath and gives it two functions. Assign BugFixAttributes to the class to record its code-maintenance history:

[BugFixAttribute(121,"Jesse Liberty","01/03/08")]
[BugFixAttribute(107,"Jesse Liberty","01/04/08",
                 Comment="Fixed off by one errors")]
public class MyMath

These attributes are stored with the metadata. Example 17-1 shows the complete program.

Example 17-1. Working with custom attributes

using System;

namespace CustomAttributes
{
    // create custom attribute to be assigned to class members
    [AttributeUsage(AttributeTargets.Class |
                    AttributeTargets.Constructor |
                    AttributeTargets.Field |
                    AttributeTargets.Method |
                    AttributeTargets.Property,
                    AllowMultiple = true)]
    public class BugFixAttribute : System.Attribute
    {
        // attribute constructor for positional parameters
        public BugFixAttribute
        (
            int bugID,
            string programmer,
            string date
        )
        {
            this.BugID = bugID;
            this.Programmer = programmer;
            this.Date = date;
        }

        // accessors
        public int BugID { get; private set; }
        public string Date { get; private set; }
        public string Programmer { get; private set; }

        // property for named parameter
        public string Comment { get; set; }
    }

    // ********* assign the attributes to the class ********

    [BugFixAttribute(121, "Jesse Liberty", "01/03/08")]
    [BugFixAttribute(107, "Jesse Liberty", "01/04/08",
                        Comment = "Fixed off by one errors")]
    public class MyMath
    {
        public double DoFunc1(double param1)
        {
            return param1 + DoFunc2(param1);
        }

        public double DoFunc2(double param1)
        {
            return param1 / 3;
        }
    }

    public class Tester
    {
        static void Main(string[] args)
        {
            MyMath mm = new MyMath();
            Console.WriteLine("Calling DoFunc(7). Result: {0}",
            mm.DoFunc1(7));
        }
    }
}

Output:
Calling DoFunc(7). Result: 9.3333333333333333

As you can see, the attributes had absolutely no impact on the output. This is not surprising because, as we said earlier, attributes are passive—they only affect things that go looking for them, and we’ve not yet written anything that does that. In fact, for the moment, you have only our word that the attributes exist at all. We’ll see how to get at this metadata and use it in a program in the next section.

Reflection

For the attributes in the metadata to be useful, you need a way to access them at runtime. The classes in the Reflection namespace, along with the System.Type class, provide support for examining and interacting with the metadata.

Reflection is generally used for any of four tasks:

Inspecting metadata

This might be used by tools and utilities that wish to display metadata, or by class library features that modify their behavior based on metadata.

Performing type discovery

Your code can examine the types in an assembly and interact with or instantiate those types. An application that supports plug-ins might use this to discover what features a plug-in DLL offers.

Late binding to methods and properties

This allows the programmer to invoke properties and methods on objects dynamically instantiated, based on type discovery. This is also known as dynamic invocation. (As we’ll see in Chapter 18, C# 4.0 has introduced an easier way to do this than using reflection.)

Creating types at runtime

You can generate new types at runtime. You might do this when a custom class containing code generated at runtime, specialized for a particular task, will run significantly faster than a more general-purpose solution. This is an advanced technique that is beyond the scope of this book.

Inspecting Metadata

In this section, we will use the C# reflection support to read the metadata in the MyMath class.

The reflection system defines numerous classes, each designed to provide information about a particular kind of metadata. For example, the ConstructorInfo provides access to all the metadata for a constructor, while PropertyInfo gives us the metadata for a property. Our custom attribute in Example 17-1 can be applied to a wide range of targets, so we’re going to encounter several different metadata types. However, all of our supported targets have something in common—they are all things that can be members of classes. (That’s plainly true for properties, methods, fields, and constructors. Our attribute can also be applied to classes, which seems like an exception because they’re often not members of other types, but the point is that they can be.) And so, the metadata types for all our supported target types derive from a common base class, MemberInfo.

MemberInfo is defined in the System.Reflection namespace. We can use it to discover the attributes of a member and to provide access to the metadata. We’ll start by getting hold of the metadata for a particular type:

System.Reflection.MemberInfo inf = typeof(MyMath);

We’re using the typeof operator on the MyMath type, which returns an object of type Type, which derives from MemberInfo.

Note

The Type class is the heart of the reflection classes. Type encapsulates a representation of the type of an object. The Type class is the primary way to access metadata—we can use it to get hold of information about the other members of a class (e.g., methods, properties, fields, events, etc.).

The next step is to call GetCustomAttributes on this MemberInfo object, passing in the type of the attribute we want to find. It returns an array of objects, each of type BugFixAttribute:

object[] attributes;
attributes =
    inf.GetCustomAttributes(typeof(BugFixAttribute),false);

You can now iterate through this array, printing out the properties of the BugFixAttribute object. Example 17-2 replaces the Main method in the Tester class from Example 17-1.

Example 17-2. Using reflection

public static void Main(string[] args)
{
    MyMath mm = new MyMath();
    Console.WriteLine("Calling DoFunc(7). Result: {0}",
                      mm.DoFunc1(7));

    // get the member information and use it to
    // retrieve the custom attributes
    System.Reflection.MemberInfo inf = typeof(MyMath);
    object[] attributes;
    attributes = inf.GetCustomAttributes(
                     typeof(BugFixAttribute), false);

    // iterate through the attributes, retrieving the
    // properties
    foreach (Object attribute in attributes)
    {
        BugFixAttribute bfa = (BugFixAttribute)attribute;
        Console.WriteLine("
BugID: {0}", bfa.BugID);
        Console.WriteLine("Programmer: {0}", bfa.Programmer);
        Console.WriteLine("Date: {0}", bfa.Date);
        Console.WriteLine("Comment: {0}", bfa.Comment);
    }
}

Output:
Calling DoFunc(7). Result: 9.3333333333333333

BugID: 121
Programmer: Jesse Liberty
Date: 01/03/08
Comment:

BugID: 107
Programmer: Jesse Liberty
Date: 01/04/08
Comment: Fixed off by one errors

When you put this replacement code into Example 17-1 and run it, you can see the metadata printed as you’d expect.

Type Discovery

You can use reflection to explore and examine the contents of an assembly. You can find the types it contains. You can discover the methods, fields, properties, and events associated with a type, as well as the signatures of each of the type’s methods. You can also discover the interfaces supported by the type, and the type’s base class.

If we were using this to support a plug-in system for extending our application, we’d need to load at runtime assemblies we didn’t know about when we wrote our application. We can load an assembly dynamically with the Assembly.Load() static method. The Assembly class encapsulates the actual assembly itself, for purposes of reflection. One signature for the Load method is:

public static Assembly Load(string assemblyName)

For example, Mscorlib.dll has the core classes of the .NET Framework, so we can pass that to the Load() method:

Assembly a = Assembly.Load("Mscorlib");

(In fact Mscorlib.dll will already be loaded, but this method doesn’t mind—it returns the assembly we asked for, loading it first if necessary.) There’s also a LoadFrom method that takes a file path. Once the assembly is loaded, we can call GetTypes() to return an array of Type objects. A Type object represents a specific type declaration, such as a class, interface, array, struct, delegate, or enumeration:

Type[] types = a.GetTypes();

The assembly returns an array of types that we can display in a foreach loop, as shown in Example 17-3. Because this example uses the Type class, you will want to add a using directive for the System.Reflection namespace.

Example 17-3. Reflecting on an assembly

using System;
using System.Reflection;

namespace ReflectingAnAssembly
{
    public class Tester
    {
        public static void Main()
        {
            // what is in the assembly
            Assembly a = Assembly.Load("Mscorlib");
            Type[] types = a.GetTypes();
            foreach (Type t in types)
            {
                Console.WriteLine("Type is {0}", t);
            }
            Console.WriteLine(
                "{0} types found", types.Length);
        }
    }
}

The output from this would fill many pages. Here is a short excerpt:

Type is System.Object
Type is ThisAssembly
Type is AssemblyRef
Type is System.ICloneable
Type is System.Collections.IEnumerable
Type is System.Collections.ICollection
Type is System.Collections.IList
Type is System.Array

This example obtained an array filled with the types from the core library and printed them one by one. The array contained 2,779 entries when run against .NET version 4.0.

Reflecting on a Specific Type

Instead of iterating through all the types, you can ask the reflection system for a single specific one. This may seem odd—if you already know what type you want, why would you need to use reflection to find things out about it at runtime? In fact, this can be useful for several reasons—some applications let users put the name of a type in a configuration file, so the program only discovers the name of the type it requires at runtime, and wants to look up just that one type. To do so, you extract a type from the assembly with the GetType() method, as shown in Example 17-4.

Example 17-4. Reflecting on a type

using System;
using System.Reflection;

namespace ReflectingOnAType
{
    public class Tester
    {
        public static void Main()
        {
            // examine a single type
            Assembly a = Assembly.Load("Mscorlib");
            Type theType = a.GetType("System.Reflection.Assembly");
            Console.WriteLine("
Single Type is {0}
", theType);
        }
    }
}

Output:
Single Type is System.Reflection.Assembly

It can sometimes be useful to get hold of the Type object for a specific type known to you at compile time. This may seem odd, for the reasons described earlier, but the usual reason for doing this is not so that you can learn more about the type. You may need to do it to compare one type object with another. For example, if we wanted to find all of the types in mscorlib that derive from the MemberInfo class, we would need to get hold of the Type object for MemberInfo. Example 17-5 does this.

Example 17-5. Using a specific type object for comparison purposes

using System;
using System.Linq;
using System.Reflection;

namespace UsingASpecificType
{
    public class Tester
    {
        public static void Main()
        {
            // examine a single type
            Assembly a = Assembly.Load("Mscorlib");

            var matchingTypes = from t in a.GetTypes()
                                where typeof(MemberInfo).IsAssignableFrom(t)
                                select t;

            foreach (Type t in matchingTypes)
            {
                Console.WriteLine(t);
            }
        }
    }
}

This uses a LINQ query to find the matching types. It illustrates one of the things you can do with a Type object—its IsAssignableFrom method tells you whether it’s possible to assign an instance of one type into a field or variable of another type. So this code looks at every type, and asks whether it can be assigned into a variable of type MemberInfo. (This casts the net slightly wider than merely looking at the base class—this query will find all types that derive either directly or indirectly from MemberInfo.) Since we know exactly what target type we’re interested in, we use the C# typeof operator to get the Type object for that exact type.

Finding all type members

You can ask a Type object for all its members using the GetMembers() method of the Type class, which lists all the methods, properties, and fields, as shown in Example 17-6.

Example 17-6. Reflecting on the members of a type

using System;
using System.Reflection;

namespace ReflectingOnMembersOfAType
{
    public class Tester
    {
        public static void Main()
        {
            // examine a single type
            Assembly a = Assembly.Load("Mscorlib");
            Type theType = a.GetType("System.Reflection.Assembly");
            Console.WriteLine("
Single Type is {0}
", theType);

            // get all the members
            MemberInfo[] mbrInfoArray = theType.GetMembers();
            foreach (MemberInfo mbrInfo in mbrInfoArray)
            {
                Console.WriteLine("{0} is a {1}",
                        mbrInfo, mbrInfo.MemberType);
            }
        }
    }
}

Once again, the output is quite lengthy, but within the output you see fields, methods, constructors, and properties, as shown in this excerpt:

System.Type GetType(System.String, Boolean, Boolean) is a Method
System.Type[] GetExportedTypes() is a Method
System.Reflection.Module GetModule(System.String) is a Method
System.String get_FullName() is a Method

Finding type methods

You might want to focus on methods only, excluding the fields, properties, and so forth. To do so, find the call to GetMembers():

MemberInfo[] mbrInfoArray =
    theType.GetMembers();

and replace it with a call to GetMethods():

mbrInfoArray = theType.GetMethods();

The output now contains nothing but the methods:

Output (excerpt):
Boolean Equals(System.Object) is a Method
System.String ToString() is a Method
System.String CreateQualifiedName(
System.String, System.String) is a Method
Boolean get_GlobalAssemblyCache() is a Method

Late Binding

Once you find a method, you can invoke it using reflection. For example, you might like to invoke the Cos() method of System.Math, which returns the cosine of an angle.

Note

You can, of course, call Cos() in the normal course of your code, but reflection allows you to bind to that method at runtime. This is called late binding, and offers the flexibility of choosing at runtime which object to bind to and invoking it programmatically. The dynamic keyword added in C# 4.0, discussed in Chapter 18, can do this for you, but you may sometimes want to control the underlying mechanisms for late binding yourself. This can be useful when creating a custom script to be run by the user or when working with objects that might not be available at compile time.

To invoke Cos(), first get the Type information for the System.Math class:

Type theMathType = typeof(System.Math);

Once we have type information, we could dynamically create an instance of the type using a static method of the Activator class. However, we don’t need to here because Cos() is static. In fact, all members of System.Math are static, and even if you wanted to create an instance, you can’t because System.Math has no public constructor. However, since you will come across types that need to be instantiated so that you can call their nonstatic members, it’s important to know how to create new objects with reflection.

The Activator class contains three methods, all static, which you can use to create objects. The methods are as follows:

CreateComInstanceFrom

Creates instances of COM objects.

CreateInstanceFrom

Creates a reference to an object from a particular assembly and type name.

CreateInstance

Creates an instance of a particular type from a Type object. For example:

Object theObj = Activator.CreateInstance(someType);

Back to the Cos() example. Our theMathType variable now refers to a Type object which we obtained by calling GetType.

Before we can invoke a method on the type, we must get the method we need from the Type object. To do so, we call GetMethod(), passing the method name:

MethodInfo cosineInfo =
 theMathType.GetMethod("Cos");

Note

There’s obviously a problem here if you need to deal with overloaded methods. That’s not an issue for this particular example—there’s only one Cos method. But if you need to deal with multiple methods of the same name, you can use an alternative overload of GetMethod that takes two arguments. After the method name you can pass an array of the argument types, which allows you to uniquely identify the overload you require. We could use that here if we wanted even though it’s not necessary—we could create a Type[] array containing one entry: the typeof(double). This would tell GetMethod that we are looking specifically for a method called Cos that takes a single argument of type double.

You now have an object of type MethodInfo which provides an Invoke method that calls the method this MethodInfo represents. Normally, the first argument to Invoke would be the object on which we want to invoke the method. However, because this is a static method, there is no object, so we just pass null. And then we pass the arguments for the function. Invoke is capable of calling any method, regardless of how many arguments it has, so it expects the arguments to be wrapped in an array, even if there’s only one:

Object[] parameters = new Object[1];
parameters[0] = 45 * (Math.PI/180); // 45 degrees in radians
Object returnVal = cosineInfo.Invoke(null, parameters);

Example 17-7 shows all the steps required to call the Cos() method dynamically.

Example 17-7. Dynamically invoking a method

using System;
using System.Reflection;

namespace DynamicallyInvokingAMethod
{
    public class Tester
    {
        public static void Main()
        {
            Type theMathType = Type.GetType("System.Math");
            // Since System.Math has no public constructor, this
            // would throw an exception.
            // Object theObj =
            //     Activator.CreateInstance(theMathType);

            // array with one member
            Type[] paramTypes = new Type[1];
            paramTypes[0] = Type.GetType("System.Double");

            // Get method info for Cos()
            MethodInfo CosineInfo =
                theMathType.GetMethod("Cos", paramTypes);

            // fill an array with the actual parameters
            Object[] parameters = new Object[1];
            parameters[0] = 45 * (Math.PI / 180); // 45 degrees in radians
            Object returnVal =
                CosineInfo.Invoke(theMathType, parameters);
            Console.WriteLine(
                "The cosine of a 45 degree angle {0}",
                returnVal);
        }
    }
}

Output:
The cosine of a 45 degree angle 0.707106781186548

That was a lot of work just to invoke a single method. The power, however, is that you can use reflection to discover an assembly on the user’s machine, to query what methods are available, and to invoke one of those members dynamically. Chapter 18 will show how you can use the dynamic keyword to automate this for certain scenarios.

Summary

All .NET components contain metadata. Some of this is essential information about the structure of our code—the metadata includes the list of types, their names, the members they define, the arguments accepted by the methods, and so on. But the metadata system is also extensible—attributes can be embedded alongside the core metadata, and these can then be discovered at runtime. Finally, we saw that some metadata features can make use of the items they represent—we can use method information to invoke a method we discovered dynamically, for example.



[48] Modules are the individual files that constitute an assembly. The vast majority of assemblies consist of just one file, so it’s very rare to encounter situations in which you need to deal with an individual module as opposed to the whole assembly. They are mentioned here for completeness.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset