Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

.pdf
Скачиваний:
78
Добавлен:
16.08.2013
Размер:
22.5 Mб
Скачать

552CHAPTER 16 TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING

class Program

{

static void Main(string[] args)

{

Console.WriteLine("***** Descriptions of Your Vehicles *****\n");

//Load the local copy of AttributedCarLibrary.

Assembly asm = Assembly.Load("AttributedCarLibrary");

//Get type info of VehicleDescriptionAttribute.

Type vehicleDesc = asm.GetType("AttributedCarLibrary.VehicleDescriptionAttribute");

//Get type info of the Description property.

PropertyInfo propDesc = vehicleDesc.GetProperty("Description");

//Get all types in the assembly. Type[] types = asm.GetTypes();

//Iterate over each type and obtain any VehicleDescriptionAttributes. foreach (Type t in types)

{

object[] objs = t.GetCustomAttributes(vehicleDesc, false);

//Iterate over each VehicleDescriptionAttribute and print

//the description using late binding.

foreach (object o in objs)

{

Console.WriteLine("-> {0}: {1}\n", t.Name, propDesc.GetValue(o, null));

}

}

Console.ReadLine();

}

}

If you were able to follow along with the examples in this chapter, this Main() method should be (more or less) self-explanatory. The only point of interest is the use of the PropertyInfo. GetValue() method, which is used to trigger the property’s accessor. Figure 16-10 shows the output.

Figure 16-10. Reflecting on attributes using late binding

CHAPTER 16 TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING

553

Source Code The VehicleDescriptionAttributeReaderLateBinding project is included under the Chapter 16 subdirectory.

Putting Reflection, Late Binding, and Custom

Attributes in Perspective

Even though you have seen numerous examples of these techniques in action, you may still be wondering when to make use of reflection, dynamic loading, late binding, and custom attributes in your programs. To be sure, these topics can seem a bit on the academic side of programming (which may or may not be a bad thing, depending on your point of view). To help map these topics to a real-world situation, you need a solid example. Assume for the moment that you are on a programming team that is building an application with the following requirement:

• The product must be extendable by the use of additional third-party tools.

So, what exactly is meant by extendable? Consider the Visual Studio 2008 IDE. When this application was developed, various “hooks” were inserted to allow other software vendors to snap custom modules into the IDE. Obviously, the Visual Studio 2008 development team had no way to set references to external .NET assemblies it had not developed yet (thus, no early binding), so how exactly would an application provide the required hooks? Here is one possible way to solve this problem:

First, an extendable application must provide some input vehicle to allow the user to specify the module to plug in (such as a dialog box or command-line flag). This requires dynamic loading.

Second, an extendable application must be able to determine whether the module supports the correct functionality (such as a set of required interfaces) in order to be plugged into the environment. This requires reflection.

Finally, an extendable application must obtain a reference to the required infrastructure (such as a set of interface types) and invoke the members to trigger the underlying functionality. This may require late binding.

Simply put, if the extendable application has been preprogrammed to query for specific interfaces, it is able to determine at runtime whether the type can be activated. Once this verification test has been passed, the type in question may support additional interfaces that provide a polymorphic fabric to their functionality. This is the exact approach taken by the Visual Studio 2008 team, and despite what you may be thinking, is not at all difficult.

Building an Extendable Application

In the sections that follow, I will take you through a complete example that illustrates the process of building an extendable Windows Forms application that can be augmented by the functionality of external assemblies. What I will not do at this point is comment on the process of programming Windows Forms applications (see Chapter 27 for an overview of the Windows Forms API). So, if you are not familiar with the process of building Windows Forms applications, feel free to simply open up the supplied sample code and follow along. To serve as a road map, our extendable application entails the following assemblies:

554CHAPTER 16 TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING

CommonSnappableTypes.dll: This assembly contains type definitions that will be used by each snap-in object and will be directly referenced by the Windows Forms application.

CSharpSnapIn.dll: A snap-in written in C#, which leverages the types of

CommonSnappableTypes.dll.

VbNetSnapIn.dll: A snap-in written in Visual Basic, which leverages the types of

CommonSnappableTypes.dll.

MyExtendableApp.exe: This Windows Forms application will be the entity that may be extended by the functionality of each snap-in.

Again, this application will make use of dynamic loading, reflection, and late binding to dynamically gain the functionality of assemblies it has no prior knowledge of.

Building CommonSnappableTypes.dll

The first order of business is to create an assembly that contains the types that a given snap-in must leverage to be plugged into the expandable Windows Forms application. The CommonSnappableTypes Class Library project defines two types:

namespace CommonSnappableTypes

{

public interface IAppFunctionality

{

void DoIt();

}

[AttributeUsage(AttributeTargets.Class)]

public sealed class CompanyInfoAttribute : System.Attribute

{

private string companyName; private string companyUrl; public CompanyInfoAttribute(){}

public string Name

{

get { return companyName; } set { companyName = value; }

}

public string Url

{

get { return companyUrl; } set { companyUrl = value; }

}

}

}

The IAppFunctionality interface provides a polymorphic interface for all snap-ins that can be consumed by the extendable Windows Forms application. Given that this example is purely illustrative, you supply a single method named DoIt(). A more realistic interface (or a set of interfaces) might allow the object to generate scripting code, render an image onto the application’s toolbox, or integrate into the main menu of the hosting application.

The CompanyInfoAttribute type is a custom attribute that will be applied on any class type that wishes to be snapped in to the container. As you can tell by the definition of this class, [CompanyInfo] allows the developer of the snap-in to provide some basic details about the component’s point of origin.

CHAPTER 16 TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING

555

Building the C# Snap-In

Next up, you need to create a type that implements the IAppFunctionality interface. Again, to focus on the overall design of an extendable application, a trivial type is in order. Assume a new C# Class Library project named CSharpSnapIn defines a class type named CSharpModule. Given that this class must make use of the types defined in CommonSnappableTypes, be sure to set a reference to this binary (as well as System.Windows.Forms.dll to display a noteworthy message). This being said, here is the code:

using System;

using CommonSnappableTypes; using System.Windows.Forms;

namespace CSharpSnapIn

{

[CompanyInfo(Name = "Intertech Training", Url = "www.intertech.com")]

public class CSharpModule : IAppFunctionality

{

void IAppFunctionality.DoIt()

{

MessageBox.Show("You have just used the C# snap in!");

}

}

}

Notice that I choose to make use of explicit interface implementation when supporting the IAppFunctionality interface. This is not required; however, the idea is that the only part of the system that needs to directly interact with this interface type is the hosting Windows application. By explicitly implementing this interface, the DoIt() method is not directly exposed from the

CSharpModule type.

Building the Visual Basic Snap-In

Now, to simulate the role of a third-party vendor who prefers Visual Basic over C#, create a new Visual Basic code library (VbNetSnapIn) that references the same external assemblies as the previous CSharpSnapIn project. The code is (again) intentionally simple:

Imports System.Windows.Forms

Imports CommonSnappableTypes

<CompanyInfo(Name:="Chucky's Software", Url:="www.ChuckySoft.com")> _

Public Class VbNetSnapIn

Implements IAppFunctionality

Public Sub DoIt() Implements CommonSnappableTypes.IAppFunctionality.DoIt

MessageBox.Show("You have just used the VB .NET snap in!")

End Sub

End Class

Notice that applying attributes in the syntax of Visual Basic requires angle brackets (< >) rather than square brackets ([ ]). Also notice that the Implements keyword is used to implement interface types on a given class or structure.

556 CHAPTER 16 TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING

Building an Extendable Windows Forms Application

The final step is to create a new Windows Forms application (MyExtendableApp) that allows the user to select a snap-in using a standard Windows Open dialog box. Next, set a reference to the

CommonSnappableTypes.dll assembly, but not the CSharpSnapIn.dll or VbNetSnapIn.dll code libraries. Remember that the whole goal of this application is to make use of late binding and reflection to determine the “snapability” of independent binaries created by third-party vendors.

Again, I won’t bother to examine all the details of Windows Forms development at this point in the text. However, assuming you have placed a MenuStrip component onto the forms designer, define a topmost menu item named File that provides a single submenu named Snap In Module. As well, the main window will contain a ListBox type (which I renamed as lstLoadedSnapIns) that will be used to display the names of each snap-in loaded by the user. Figure 16-11 shows the final GUI.

Figure 16-11. GUI for MyExtendableApp

The code that handles the Click event for the File Snap In Module menu item (which may be created simply by double-clicking the menu item from the design-time editor) displays a File Open dialog box and extracts the path to the selected file. Assuming the user did not select the CommonSnappableTypes.dll assembly (as this is purely infrastructure), the path is then sent into a helper function named LoadExternalModule() for processing. This method will return false when it is unable to find a class implementing IAppFunctionality:

private void snapInModuleToolStripMenuItem_Click(object sender, EventArgs e)

{

// Allow user to select an assembly to load.

OpenFileDialog dlg = new OpenFileDialog();

if (dlg.ShowDialog() == DialogResult.OK)

{

if(dlg.FileName.Contains("CommonSnappableTypes")) MessageBox.Show("CommonSnappableTypes has no snap-ins!");

else if(!LoadExternalModule(dlg.FileName)) MessageBox.Show("Nothing implements IAppFunctionality!");

}

}

CHAPTER 16 TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING

557

The LoadExternalModule() method performs the following tasks:

Dynamically loads the selected assembly into memory

Determines whether the assembly contains any types implementing IAppFunctionality

Creates the type using late binding

If a type implementing IAppFunctionality is found, the DoIt() method is called, and the fully qualified name of the type is added to the ListBox (note that the foreach loop will iterate over all types in the assembly to account for the possibility that a single assembly has multiple snap-ins). Finally, notice that we are making use of a LINQ query to obtain IAppFunctionality-compatible class types.

private bool LoadExternalModule(string path)

{

bool foundSnapIn = false; Assembly theSnapInAsm = null;

try

{

// Dynamically load the selected assembly. theSnapInAsm = Assembly.LoadFrom(path);

}

catch(Exception ex)

{

MessageBox.Show(ex.Message); return foundSnapIn;

}

// Get all IAppFunctionality compatible classes in assembly. var theClassTypes = from t in theSnapInAsm.GetTypes()

where t.IsClass && (t.GetInterface("IAppFunctionality") != null) select t;

// Now, create the object and call DoIt() method. foreach (Type t in theClassTypes)

{

foundSnapIn = true;

// Use late binding to create the type.

IAppFunctionality itfApp = (IAppFunctionality)theSnapInAsm.CreateInstance(t.FullName, true);

itfApp.DoIt();

lstLoadedSnapIns.Items.Add(t.FullName);

}

return foundSnapIn;

}

At this point, you can run your application. When you select the CSharpSnapIn.dll or VbNetSnapIn.dll assemblies, you should see the correct message displayed. The final task is to display the metadata provided by the [CompanyInfo] attribute. To do so, update LoadExternalModule() to call a new helper function named DisplayCompanyData() before exiting the foreach scope. Notice this method takes a single System.Type parameter.

private bool LoadExternalModule(string path)

{

...

foreach (Type t in theClassTypes)

558 CHAPTER 16 TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING

{

...

// Show company info.

DisplayCompanyData(t);

}

return foundSnapIn;

}

Using the incoming type, simply reflect over the [CompanyInfo] attribute:

private void DisplayCompanyData(Type t)

{

// Get [CompanyInfo] data.

var compInfo = from ci in t.GetCustomAttributes(false) where (ci.GetType() == typeof(CompanyInfoAttribute)) select ci;

// Show data.

foreach (CompanyInfoAttribute c in compInfo)

{

MessageBox.Show(c.Url,

string.Format("More info about {0} can be found at", c.Name));

}

}

Figure 16-12 shows one possible run.

Figure 16-12. Snapping in external assemblies

Excellent! That wraps up the example application. I hope at this point you can see that the topics presented in this chapter can be quite helpful in the real world and are not limited to the tool builders of the world.

Source Code The CommonSnappableTypes, CSharpSnapIn, VbNetSnapIn, and MyExtendableApp projects are included under the Chapter 16 subdirectory.

CHAPTER 16 TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING

559

Summary

Reflection is a very interesting aspect of a robust OO environment. In the world of .NET, the keys to reflection services revolve around the System.Type class and the System.Reflection namespace. As you have seen, reflection is the process of placing a type under the magnifying glass at runtime to understand the who, what, where, when, why, and how of a given item.

Late binding is the process of creating a type and invoking its members without prior knowledge of the specific names of said members. Late binding is often a direct result of dynamic loading, which allows you to load a .NET assembly into memory programmatically. As shown during this chapter’s extendable application example, this is a very powerful technique used by tool builders as well as tool consumers. This chapter also examined the role of attribute-based programming. When you adorn your types with attributes, the result is the augmentation of the underlying assembly metadata.

C H A P T E R 1 7

Processes, AppDomains, and

Object Contexts

In the previous two chapters, you examined the steps taken by the CLR to resolve the location of an externally referenced assembly as well as the role of .NET metadata. In this chapter, you’ll drill deeper into the details of how an assembly is hosted by the CLR and come to understand the relationship between processes, application domains, and object contexts.

In a nutshell, application domains (or simply AppDomains) are logical subdivisions within a given process that host a set of related .NET assemblies. As you will see, an AppDomain is further subdivided into contextual boundaries, which are used to group together like-minded .NET objects. Using the notion of context, the CLR is able to ensure that objects with special runtime requirements are handled appropriately.

Reviewing Traditional Win32 Processes

The concept of a “process” has existed within Windows-based operating systems well before the release of the .NET platform. Simply put, process is the term used to describe the set of resources (such as external code libraries and the primary thread) and the necessary memory allocations used by a running application. For each *.exe loaded into memory, the OS creates a separate and isolated process for use during its lifetime. Using this approach to application isolation, the result is a much more robust and stable runtime environment, given that the failure of one process does not affect the functioning of another.

Now, every Win32 process is assigned a unique process identifier (PID) and may be independently loaded and unloaded by the OS as necessary (as well as programmatically using Win32 API calls). As you may be aware, the Processes tab of the Windows Task Manager utility (activated via the Ctrl+Shift+Esc keystroke combination) allows you to view various statistics regarding the processes running on a given machine, including its PID and image name (see Figure 17-1).

Note The View Select Columns menu option of the Windows Task Manager allows you to select which columns (PID, User Name, etc.) you wish to have displayed.

561