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.
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.
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.
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 |
---|---|
| Any of the following elements: assembly, class, constructor, delegate, enum, event, field, interface, method, module, parameter, property, return value, or struct |
| An assembly |
| A class |
| A constructor |
| A delegate |
| An enumeration |
| An event |
| A field |
| A type parameter for a generic class or method |
| An interface |
| A method |
| A module |
| A parameter of a method |
| A property (both
|
| A return value |
| A struct |
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]
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.
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.
This may be a somewhat artificial example, however, because you might not really want this information to be compiled into the shipping code.
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.
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
.
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; }
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; }
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
BugFixAttribute
s 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.
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:
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.
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.
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.)
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.
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
.
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.
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.
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.
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
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
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.
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:
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");
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.
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.