BOOM! There's one! BOOM! There's another! What do you mean you don't know where they are? You haven't missed one yet!
I've been plumbing the depths of .NET reflection lately and it felt just like Daffy's trip: I knew where I was and where I wanted to be, but I hit every landmine on my way there. It's not Microsoft's fault, really. The .NET reflection is a powerful beast and covering every little "gotcha" is a heroic work, one that the guys in charge of MSDN almost pulled off. Nearly everything you'll need to survive is somewhere in the library. If you're trying to do something simple, you'll be fine; if you're trying to pull off something advanced, you'll need a map of the minefield. Here are some of the mines I hit so far.
Attributes: Not What It Looks Like, Honey
It's easy to think of attributes as something that gets attached to other elements of your code when it's compiled. Unfortunately, it's also incorrect. Consider the following code:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class UniqueIDAttribute : Attribute { private static int nextID = 1; public int ID { get; private set; } public UniqueIDAttribute() { ID = nextID++; } } public class Base { [UniqueID] public virtual void MyMethod() { } } public class Derived : Base { public override void MyMethod() { base.MyMethod(); } } class Program { static void Main(string[] args) { UniqueIDAttribute attr; attr = (UniqueIDAttribute) Attribute.GetCustomAttribute( typeof(Derived).GetMethod("MyMethod"), typeof(UniqueIDAttribute)); Console.Write(attr.ID); Console.Write(" "); attr = (UniqueIDAttribute) Attribute.GetCustomAttribute( typeof(Base).GetMethod("MyMethod"), typeof(UniqueIDAttribute)); Console.WriteLine(attr.ID); } }
I was honestly surprised that the output of this was "1 2" instead of "1 1" I expected. I thought that placing a custom attribute on a method will attach an instance of that attribute to that method. This is not feasible for a variety of good reasons; for example, how do you handle a case of an attribute placed on a member of a generic class? Most of these problems could be worked around with lots of effort, but Microsoft decided to do things the easy way and invest that effort somewhere else.
Here's what happens when you place an attribute on a code element: the compiler takes all the information necessary to construct that attribute and emits it together with that element. When you query the attribute at run time, the reflection code pulls out this information and uses it to construct the instance of the attribute at that moment. As an example, consider the following code:
[AttributeUsage(AttributeTargets.Method)] public class SampleAttribute : Attribute { public int Value { get; protected set; } public string Comment { get; set; } public SampleAttribute(int value) { Value = value; } } public class SomeClass { [Sample(17, Comment = "Some text")] public void SomeMethod() { } }
Now take a look at the IL for SomeMethod:
.method public hidebysig instance void SomeMethod() cil managed { .custom instance void Sandbox.CSharp.SampleAttribute::.ctor(int32) = ( 01 00 11 00 00 00 01 00 54 0E 07 43 6F 6D 6D 65 // ........T..Comme 6E 74 09 53 6F 6D 65 20 74 65 78 74 ) // nt.Some text // Code size 2 (0x2) .maxstack 8 IL_0000: nop IL_0001: ret } // end of method Base::SomeMethod
As you can see, the method metadata contains custom attribute information that tells the runtime which constructor to call, what parameter to supply to it and which property to set to which value.
Having understood all that, it's no problem to understand why the CustomAttributeBuilder class exists or how to use it.
Non-Virtual Interface: The Dark Secret
Have you ever wondered how it is that you can implement an interface method in a class, without marking it as virtual? It had been bothering me since I learned C#, but I never really had a compelling reason to solve that particular mystery. It was enough to know that it Just Worked. At least, it was until I tried to dynamically create a class and make it implement an interface too.
I'll stop here to make a quick aside for readers who know only C# (and/or Java) and might wonder why I think that interface methods should necessarily be virtual. A virtual method is late-bound: you don't know at compile time which implementation will be invoked when someone calls the method. Because of that, there's a layer of indirection between the method declaration and the method body; the class that defines the body of a virtual method -- either for the first time or by overriding it -- "configures" this layer of indirection to "point" to that particular body. An interface is nothing but a bunch of abstract (and therefore virtual) methods. That's why it seems counter-intuitive to implement an interface method without specifying the "virtual" keyword.
If you try to run the following code, you'll get an interesting exception:
public interface IMyInterface { void MyMethod(); } class Program { static void Main(string[] args) { AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly( new AssemblyName() { Name = "MyAssembly" }, AssemblyBuilderAccess.Run); ModuleBuilder module = assembly.DefineDynamicModule("MyModule"); TypeBuilder myClass = module.DefineType( "MyClass", TypeAttributes.Class | TypeAttributes.Public, typeof(Object), new Type[] { typeof(IMyInterface) }); MethodBuilder myMethod = myClass.DefineMethod( "MyMethod", MethodAttributes.Public | MethodAttributes.HideBySig, typeof(void), Type.EmptyTypes); ILGenerator il = myMethod.GetILGenerator(); il.Emit(OpCodes.Ret); Type myClassType = myClass.CreateType(); } }
The exception you'll get is TypeLoadException and it'll be complaining about MyClass not implementing MyMethod. If you add the MethodAttributes.Virtual flag to the method attributes, the problem will disappear.
But what happens if you don't want MyMethod to be virtual? That's not really an option. For the reasons I explained above, the method must be virtual. What you might want is to make sure it cannot be overridden. To do that, you have to also add the MethodAttributes.Final flag to the method attributes. That will give you the equivalent of writing:
public class MyClass : IMyInterface { public void MyMethod() { } }
The funny thing is that you can't declare a method as virtual sealed, yet that's exactly what the compiler does under the hood. I guess it's just one of those trade-offs: either you use a slightly leaky abstraction or you risk antagonizing people by making them learn the underlying concepts thoroughly.
Naming Your Type: Basic Hygiene
Isn't it funny how something as useful as the string type can also cause so much trouble? Sooner or later everyone has to learn to sanitize their strings or risk having their software let the little Bobby Tables wreak havoc on their data:
I must admit, though, that I didn't expect to have to sanitize the name when creating a type at run time. I found out, quite unexpectedly, that TypeBuilder seems to be allergic to types whose names contain a comma. Not that it blows up right away. You can use a name riddled with commas and still be fine when you create the type. What you can't do is use that type as a parent type. If you do, TypeBuilder will blow up with a rather unhelpful COMException saying "Record not found on lookup."
If you google for it, you'll eventually find out that there's a bug in reflection, supposedly triggered by strings that contain any of the characters []*&+,\ in the type name. My experiments indicate that the only problematic character seems to be the comma, but I'm getting rid of all of those characters anyway.
It's a nasty little bug and it's not documented in MSDN. I figured I should warn people about it, since it's such a comma mistake after all.
Metadata Tokens: Remembering Past Lives
There are two stages in the life of a dynamic class: before and after the call to CreateType. In the first stage, you're using the TypeBuilder to define type features, such as fields, properties, methods, nested types, etc. When you're done defining your type, you call the CreateType method and the magic reflection fairy turns your TypeBuilder into a real live Type. But your little Pinocchio can either be a marionette or a real boy, and never the twain shall meet.
In practice, this means that a FieldBuilder you get from calling DefineField on your TypeBuilder can only be used to define the field. Even though the FieldBuilder inherits from FieldInfo, you cannot use it to get or set the field value on the instances of the type obtained by calling CreateType on the TypeBuilder. If you try to do so, you'll get a NotSupportedException, informing you that the "invoked member is not supported in a dynamic module". Same thing applies to other builder classes.
Looking around the documentation, you'll see that the usual workaround seems to be to look up the member by its name in the newly created type. That can get ugly, though. For example, consider what you would have to do to look up a one of the several methods with the same name. The infuriating thing is that you already have the MethodBuilder for it, which is the equivalent of a MethodInfo in its TypeBuilder, so there should be a relatively painless way of getting its counterpart in a live Type.
It turns out that there is a rather painless way of doing it and it's not even a dirty hack. Every type and every member in has a metadata token that identifies it uniquely within its module. If you have the metadata token for a method, you can resolve it into a MethodBase by calling ResolveMethod on the method's Module. The usual way of obtaining a metadata token is via the MetadataToken property defined in the MemberInfo class. That won't work on a builder class, though. You'll get an InvalidOperationException complaining that the "operation is not valid due to the current state of the object".
In order to get the metadata token from a builder class, you have to use a corresponding method in the ModuleBuilder class. For example, if you have a MethodBuilder and want to get its metadata token, you have to call GetMethodToken on its ModuleBuilder. When you put it all together, converting a MethodBuilder for a TypeBuilder into its counterpart MethodInfo for the resulting live Type can be done like this:
public static MethodInfo GetLiveMethod(MethodBuilder method) { return (MethodInfo) method.Module.ResolveMethod( ((ModuleBuilder) method.Module).GetMethodToken(method).Token); }
Clean, concise and probably more efficient.
Generic Parameters: Working Around The Taint
When I was checking out Ruby, I ran into this neat concept of taint. It stuck with me, probably because I read about it in The Pragmatic Programmer's Guide, whose style is rather memorable. And, of course, because the concept is so neat.
What, then, is taint? Besides being "the opposite of tis", 'tis a way of keeping little Bobby Tables out of mischief. The idea is simple: anything coming from the outside user is considered tainted and anything derived from a tainted value is also considered tainted; if taint checking is active, the tainted values cannot be used for anything dangerous, such as SQL queries or host operating system commands.
What does this have to do with .NET reflection? The generic parameters for dynamic types and dynamic methods behave in a similar fashion. Specifically, if you call an otherwise innocent method MakeGenericType on an existing generic Type, you'll wind up with a Type with limited capabilities. Calls like GetField will fail on such a Type.
The workaround is actually documented in MSDN, which is why I saved this particular landmine for the end. If you've already read this far, chances are you're going to read and remember this solution before you actually run into the problem.
Here's what you have to do instead of calling GetField on the "tainted" Type. First get the FieldInfo from the generic type itself, as in:
FieldInfo myField = typeof(MyGenericType<>).GetField("MyField");
Then, get the "tainted" type:
Type constructedType = typeof(MyGenericType<>).MakeGenericType(myGenericParamBuilder);
Finally, do the workaround magic:
myField = TypeBuilder.GetField(constructedType, myField);
Rinse and repeat until you run out of "taint".
Further KABOOM Avoidance
The .NET reflection is really powerful and flexible, but it can be a real pain in the backside if you're not careful. Now that C# 3 is here, you can opt to use the Expression<TDelegate> instead of old DynamicMethod, but the old-style TypeBuilder is not likely to go anywhere soon. I hope that this map will help you get across the minefield without getting blown up often.
4 comments:
Offtopic: Can you please send me your email address that I can use to contact you? Thanks.
Sure, Eugene. To what e-mail address should I send you that info? ;)
It seems you are not much not into blogging buddy! I wish to read some more!
Thanks for the comment, Bay. That actually motivated me to publish something I've been thinking about for a few weeks now.
Unfortunately, with all the stuff I'm trying to do, I don't have much time for blogging. It usually takes me a day to write a post that's up to my standards. As a result, whenever I feel I might want to blog about a certain topic, I spend some time thinking about it first and, in most cases, I realize that I don't really know how to make it worth the while and so I drop it ;)
Post a Comment