
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf
404 C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G
Figure 12-4. Reflecting on the external CarLibrary assembly
If you wish to make ExternalAssemblyReflector more flexible, load the external assembly using Assembly.LoadFrom() rather than Assembly.Load(). By doing so, you can enter an absolute path to the assembly you wish to view (e.g., C:\MyApp\MyAsm.dll).
■Source Code The ExternalAssemblyReflector project is included in the Chapter 12 subdirectory.
Reflecting on Shared Assemblies
As you may suspect, Assembly.Load() has been overloaded a number of times. One variation of the Assembly.Load() method allows you to specify a culture value (for localized assemblies) as well as a version number and public key token value (for shared assemblies).
Collectively speaking, the set of items identifying an assembly is termed the display name. The format of a display name is a comma-delimited string of name/value pairs that begins with the friendly name of the assembly, followed by optional qualifiers (that may appear in any order). Here is the template to follow (optional items appear in parentheses):
Name (,Culture = culture token) (,Version = major.minor.build.revision) (,PublicKeyToken= public key token)
When you’re crafting a display name, the convention PublicKeyToken=null indicates that binding and matching against a non–strongly-named assembly is required. Additionally, Culture="" indicates matching against the default culture of the target machine, for example:
// Load version 1.0.982.23972 of CarLibrary using the default culture.
Assembly a = Assembly.Load(
@"CarLibrary, Version=1.0.982.23972, PublicKeyToken=null, Culture=""");
Also be aware that the System.Reflection namespace supplies the AssemblyName type, which allows you to represent the preceding string information in a handy object variable. Typically, this class is used in conjunction with System.Version, which is an OO wrapper around an assembly’s version number. Once you have established the display name, it can then be passed into the overloaded Assembly.Load() method:
// Make use of AssemblyName to define the display name.
AssemblyName asmName;


406C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G
“cool factor,” you likely will not need to build custom object browsers at your place of employment. Do recall, however, that reflection services are the foundation for a number of very common programming activities, including late binding.
Understanding Late Binding
Simply put, late binding is a technique in which you are able to create an instance of a given type and invoke its members at runtime without having compile-time knowledge of its existence. When you are building an application that binds late to a type in an external assembly, you have no reason to set a reference to the assembly; therefore, the caller’s manifest has no direct listing of the assembly.
At first glance, you may not understand the value of late binding. It is true that if you can “bind early” to a type (e.g., set an assembly reference and allocate the type using the C# new keyword), you should opt to do so. For one reason, early binding allows you to determine errors at compile time, rather than at runtime. Nevertheless, late binding does have a critical role in any extendable application you may be building.
The System.Activator Class
The System.Activator class is the key to .NET late binding process. Beyond the methods inherited from System.Object, Activator defines only a small set of members, many of which have to do with
.NET remoting (see Chapter 18). For our current example, we are only interested in the Activator. CreateInstance() method, which is used to create an instance of a type à la late binding.
This method has been overloaded numerous times to provide a good deal of flexibility. The simplest variation of the CreateInstance() member takes a valid Type object that describes the entity you wish to allocate on the fly. Create a new application named LateBinding, and update the Main() method as so (be sure to place a copy of CarLibrary.dll in the project’s \Bin\Debug directory):
// Create a type dynamically. public class Program
{
static void Main(string[] args)
{
//Try to load a local copy of CarLibrary.
Assembly a = null; try
{
a = Assembly.Load("CarLibrary");
}
catch(FileNotFoundException e)
{
Console.WriteLine(e.Message);
Console.ReadLine();
return;
}
//Get metadata for the Minivan type.
Type miniVan = a.GetType("CarLibrary.MiniVan");
// Create the Minivan on the fly.
object obj = Activator.CreateInstance(miniVan);
}
}



C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G |
409 |
cover the presence of various attributes during the compilation cycle. For example, if the C# compiler encounters the [CLSCompilant] attribute, it will automatically check the attributed item to ensure it is exposing only CLS-compliant constructs. By way of another example, if the C# compiler discovers an item attributed with the [Obsolete] attribute, it will display a compiler warning in the Visual Studio 2005 Error List window.
In addition to development tools, numerous methods in the .NET base class libraries are preprogrammed to reflect over specific attributes. For example, if you wish to persist the state of an object to file, all you are required to do is annotate your class with the [Serializable] attribute. If the Serialize() method of the BinaryFormatter class encounters this attribute, the object is automatically persisted to file in a compact binary format.
The .NET CLR is also on the prowl for the presence of certain attributes. Perhaps the most famous
.NET attribute is [WebMethod]. If you wish to expose a method via HTTP requests and automatically encode the method return value as XML, simply apply [WebMethod] to the method and the CLR handles the details. Beyond web service development, attributes are critical to the operation of the .NET security system, .NET remoting layer, and COM/.NET interoperability (and so on).
Finally, you are free to build applications that are programmed to reflect over your own custom attributes as well as any attribute in the .NET base class libraries. By doing so, you are essentially able to create a set of “keywords” that are understood by a specific set of assemblies.
Applying Predefined Attributes in C#
As previously mentioned, the .NET base class library provides a number of attributes in various namespaces. Table 12-3 gives a snapshot of some—but by absolutely no means all—predefined attributes.
Table 12-3. A Tiny Sampling of Predefined Attributes
Attribute |
Meaning in Life |
[CLSCompliant] |
Enforces the annotated item to conform to the rules of the Common |
|
Language Specification (CLS). Recall that CLS-compliant types are |
|
guaranteed to be used seamlessly across all .NET programming |
|
languages. |
[DllImport] |
Allows .NET code to make calls to any unmanaged C- or C++-based code |
|
library, including the API of the underlying operating system. Do note |
|
that [DllImport] is not used when communicating with COM-based |
|
software. |
[Obsolete] |
Marks a deprecated type or member. If other programmers attempt to use |
|
such an item, they will receive a compiler warning describing the error of |
|
their ways. |
[Serializable] |
Marks a class or structure as being “serializable.” |
[NonSerialized] |
Specifies that a given field in a class or structure should not be persisted |
|
during the serialization process. |
[WebMethod] |
Marks a method as being invokable via HTTP requests and instructs the |
|
CLR to serialize the method return value as XML (see Chapter 25 for |
|
complete details). |
|
|
To illustrate the process of applying attributes in C#, assume you wish build a class named Motorcycle that can be persisted in a binary format. To do so, simply apply the [Serializable] attribute to the class definition. If you have a field that should not be persisted, you may apply the
[NonSerialized] attribute:

410 C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G
// This class can be saved to disk. [Serializable]
public class Motorcycle
{
//However this field will not be persisted. [NonSerialized]
float weightOfCurrentPassengers;
//These fields are still serializable. bool hasRadioSystem;
bool hasHeadSet; bool hasSissyBar;
}
■Note An attribute only applies to the “very next” item. For example, the only nonserialized field of the Motorcycle class is weightOfCurrentPassengers. The remaining fields are serializable given that the entire class has been annotated with [Serializable].
At this point, don’t concern yourself with the actual process of object serialization (Chapter 17 examines the details). Just notice that when you wish to apply an attribute, the name of the attribute is sandwiched between square brackets.
Once this class has been compiled, you can view the extra metadata using ildasm.exe. Notice that these attributes are recorded using the serializable and notserialized tokens (see Figure 12-6).
Figure 12-6. Attributes shown in ildasm.exe
As you might guess, a single item can be attributed with multiple attributes. Assume you have a legacy C# class type (HorseAndBuggy) that was marked as serializable, but is now considered obsolete for current development. To apply multiple attributes to a single item, simply use
a comma-delimited list:
[Serializable,
Obsolete("This class is obsolete, use another vehicle!")] public class HorseAndBuggy
{
// ...


412C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G
simplify the process of applying attributes, the C# language does not require you to type in the Attribute suffix. Given this, the following iteration of the HorseAndBuggy type is identical to the previous (it just involves a few more keystrokes):
[SerializableAttribute]
[ObsoleteAttribute("This class is obsolete, use another vehicle!")] public class HorseAndBuggy
{
// ...
}
Be aware that this is a courtesy provided by C#. Not all .NET-enabled languages support this feature. In any case, at this point you should hopefully understand the following key points regarding
.NET attributes:
•Attributes are classes that derive from System.Attribute.
•Attributes result in embedded metadata.
•Attributes are basically useless until another agent reflects upon them.
•Attributes are applied in C# using square brackets.
Next up, let’s examine how you can build your own custom attributes and a piece of custom software that reflects over the embedded metadata.
Building Custom Attributes
The first step in building a custom attribute is to create a new class deriving from System.Attribute. Keeping in step with the automobile theme used throughout this book, assume you have created a brand new C# class library named AttributedCarLibrary. This assembly will define a handful of vehicles (some of which you have already seen in this text), each of which is described using a custom attribute named VehicleDescriptionAttribute:
// A custom attribute.
public sealed class VehicleDescriptionAttribute : System.Attribute
{
private string msgData;
public VehicleDescriptionAttribute(string description) { msgData = description;}
public VehicleDescriptionAttribute(){ }
public string Description
{
get { return msgData; } set { msgData = value; }
}
}
As you can see, VehicleDescriptionAttribute maintains a private internal string (msgData) that can be set using a custom constructor and manipulated using a type property (Description). Beyond the fact that this class derived from System.Attribute, there is nothing unique to this class definition.
