Showing posts with label reflection. Show all posts
Showing posts with label reflection. Show all posts

Friday, July 20, 2007

Finding the interfaces you want

This is a simple little .NET reflection trick, but one which not a lot of people realize how to do (I get asked about this a lot). Suppose you want to search an assembly for all the types that implement a given interface. For example, your application supports plug-ins and you want to load a third-party assembly that potentially contains plug-in classes that implement your plug-in interface.

C#:

Assembly assembly = Assembly.LoadFile(@"C:\path\to\3rdparty\plug-in.dll");
foreach (Type type in assembly.GetTypes())
{
if (typeof(IMyPlugin).IsAssignableFrom(type))
{
IMyPlugin plugin = (IMyPlugin)assembly.CreateInstance(type.FullName);
// do something with the plugin ...
}
}


VB.NET:

Dim assembly As Assembly = Assembly.LoadFile("C:\path\to\3rdparty\plug-in.dll");
Dim type As Type
For Each type In assembly.GetTypes()
If GetType(IMyPlugin).IsAssignableFrom(type)
Dim plugin As IMyPlugin = CType(assembly.CreateInstance(type.FullName), IMyPlugin)
' do something with the plugin ...
End If
Next


It's pretty straightforward. First, we use the Assembly object to load the third party assembly from its DLL. Then, we use the Assembly.GetTypes() method to iterate through all of the classes present in the assembly. Inside the loop, we use Type.IsAssignableFrom() to determine whether the class can be assigned to our IMyPlugin interface. Finally, we create an instance of the class and assign it to a variable of type IMyPlugin so that we can actually do something with it (note that the above code assumes the class has a parameterless constructor).

Note that you want to check the possibilty of assignment in the order shown above, i.e. typeof(interface).IsAssignableFrom(class). If you do it the other way around, it won't work. Basically this statement is testing to see if this is possible:

C#:

interface variableName = new class();


VB.NET:

Dim variableName As interface = New class()

Wednesday, January 24, 2007

When are attributes created?

Speaking of Attributes, there's often a lot of misconceptions about how they are handled by the compiler and at runtime. Some of this is because of not reading the documentation properly, but also a lot of it is compounded by the fact that there are actually different kinds of attributes that look the same but are handled differently by the compiler and the CLR.

Take, for instance, ObsoleteAttribute: this attribute, innocently enough, behaves like any other custom attribute. It is written into the compiled metadata for your assembly. It can be reflected at runtime. But, as you know, it also triggers a specific response in the compiler; it generates a warning or error informing the programmer that the marked element is deprecated.

This can generate a false conception for the developer that attributes are instantiated a compile-time by the compiler. This is not true; ObsoleteAttribute just happens to be a special case handled specifically by the compiler.

You should also be aware of a type of attribute known as a "pseudo-attribute." These attributes are not written to the compiled metadata for your assembly, and thus cannot be reflected at runtime. Again, they are typically interpreted by the compiler in some way.

For example, there is the pseudo-attribute AssemblyVersionAttribute. This attribute tells the compiler what version to assign to the final assembly produced. If you attempt to reflect this attribute at runtime, you will find that it doesn't actually exist on your assembly (you have to use Assembly.GetName().Version).

The ultimate thing you have to keep in mind is that attributes are not instantiated until someone requests them. The compiler does this on its own in certain cases, but for the most part it just does a compile-time verification of the code and then passes the attribute on as meta-data into the IL.

For an example, assume that we have defined a custom attribute called TimeStampAttribute that takes a single string in its constructor that it wants to parse into a DateTime value. We want to use this to mark when we created certain methods:

C#:

[TimeStamp("fred")]
public void MyMethod()
{
// ...
}


VB.NET:

<TimeStamp("fred")> _
Public Sub MyMethod()
' ...
End Sub


But wait! That code won't work, right? It's supposed to have a date/time that we can parse like "1/24/2007 11:46 PM" instead of "fred." We're going to get a FormatException as soon as we run, right? Wrong.

The code of course compiles just fine, since "fred" is a string like we asked for. The code will even run just fine for the most part since the attributes aren't created explicitly. You won't get the expected exception raised until you make a call to GetCustomAttributes() that ends up iterating over the attributes in question.

Tuesday, January 23, 2007

When iterating attributes, make sure you get the right thing

I'll admit I kind of dubbed out on this one. When you're trying to get the custom attributes off of an object, make sure you're getting the right object. In my case, I needed to get the Attributes of my assembly, not the Assembly class itself.

It should come as no surprise that the following code doesn't do what I expected:



object[] attribs = Assembly.GetExecutingAssembly().GetType().GetCustomAttributes(typeof(Attribute), false);



Instead I just needed to remove the call to GetType():



object[] attribs = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(Attribute), false);



The Assembly class provides a handy GetCustomAttributes() method since calling it on the Assembly type itself doesn't really help you much. The first example gets you a bunch of crazy attributes that really aren't what I was looking for; the second returns all of the assembly-decorating attributes declared in my AssemblyInfo.cs.