Chapter 24. Reflection and Creating Plugins

Reflection is generally all about getting information about code. However, using the reflection APIs, you can dynamically execute code you load from an arbitrary assembly, giving you an easy way to implement a plugin architecture in your app.

Enumerate Types in an Assembly

Solution: Reflection is most commonly used for discovery, and nearly everything in .NET is discoverable.

This snippet of code populates a System.Windows.Forms.TreeView control with classes, methods, properties, fields, and events from an assembly:

image

See the EnumerateAssemblyTypes sample project for the full source code.

Add a Custom Attribute

Solution: Define an attribute class. Attributes are used extensively in .NET. You’ve seen in Chapter 11, “Files and Serialization,” that all you need to do to get basic serialization working on a class is to add the [Serializable] attribute above the class definition. In Chapter 12, “Networking and the Web,” you saw that attributes on methods define service interfaces in WCF.

There is nothing magical about Attributes, however. All they do is attach metadata to another piece of code. It’s up to your code to extract that metadata and do something with it.

The following example shows a simple attribute (which we used in Chapter 6, “Enumerations”) that allows a developer to attach a culture string to any other program construct:

image

The AttributeTargets enumeration allows you to decide what target are valid for this attribute. The valid values are Assembly, Module, Class, Struct, Enum, Constructor, Method, Property, Field, Event, Interface, Parameter, Delegate, ReturnValue, GenericParameter, and All. If you want to combine values you can use the | operator, like this: AttributeTargets.Field | AttributeTargets.Property.

You can also specify with AllowMultiple whether multiple instances of this attribute are valid on a single element.

To apply this attribute, you use the square brackets:

image

Since every attribute ends in “Attribute,” the compiler will allow you to just specify the first part of the class name when applying them.

In order to make use of the attribute, you must write code that is aware of the CultureAttribute class and look for it.

image

This example produces the output:

Cultures of Program: en-US, en-GB

Instantiate a Class Dynamically

Solution: Using reflection, it’s possible to instantiate code from assemblies that are not referenced at build time. Suppose you have a class defined in DynamicInstantiateLib.dll:

image

In a separate assembly that does not have a reference to DynamicInstantiateLib.dll, you can use this code to nevertheless create an instance of TestClass:

image

Because you don’t have a reference, you can’t use the actual type to refer to it. This would seem to make it hard to use, but there are a few ways around this, as seen in the next section.

Invoke a Method on a Dynamically Instantiated Class

Solution: Assuming the following code, you have a few options:

image

Method 1:

image

Note that we pass the obj because Add is an instance method, and obj is that instance.

Method 2:

InvokeMember does not work for generic methods, so here’s another way (also valid for Add):

image

Method 3:

New in C# 4.0, you can use dynamic types to have the method call resolved at runtime, as essentially a shortcut syntax for the previous methods:

image

You won’t get IntelliSense with dynamic types, but this definitely looks cleaner than using MethodInfo objects and Invoke calls.

Note

Beware: Putting off type checking until runtime has its costs, mostly in unforeseen errors. Take advantage of compiler-enforced static typing whenever possible and only use dynamic typing when absolutely necessary.

Implement a Plugin Architecture

Solution: By using all the principles of this chapter, plus a few more tidbits, it’s extremely easy in .NET to create your own plugin system for your application (see Figure 24.1).

Figure 24.1 Implementing a plugin architecture is a snap with judicious use of interfaces and reflection.

image

Create a Shared Assembly

First, you need an assembly that both the plugins and the app can reference. This assembly defines the interfaces that plugins must implement and any common code you want to give them access to. For our simple example, there’s just a single interface:

image

Create a Plugin Assembly

Create a new assembly and add a reference to the previous assembly with the IImagePlugin interface. Then define this code:

image

Search for and Load Plugins

The final step is discovery, which is just loading each DLL and checking to see if there are types that implement our interface. If so, create an instance of it and add it to the menu.

The following code is an excerpt from the PluginDemo sample project:

image

image

image

Note

Making a plugin work is really about defining a good set of interfaces. For example, you could allow communication the other way by defining an interface that your application implements and passing that to the plugin, allowing it to communicate and modify the application itself.

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

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