On the .NET Framework, when we call GetType() on an object (or use the typeof operator), the documentation says it returns a Type. What most people don’t realize is that the Type class is an extensibility point in the system; in fact, most of the members of Type are abstract or virtual. When you call GetType() or use typeof, what you’re actually getting back is an instance of RuntimeType.
For the most part, developers don’t care that they’ve been handed a RuntimeType; it should be an implementation detail of the system. And, for most developers, it appears to be so; in fact, I’d estimate most developers aren’t even aware that RuntimeType exists. For most developers – including myself, until about 2 weeks ago – we’ve always thought of Type as something the system provides us. For most of us, Type == RuntimeType.
However, there are places in the .NET Framework where a RuntimeType is actually required. In our happy cases, we don’t ever realize these exist because the method signatures all talk about Type, not RuntimeType. The requirement for a RuntimeType actually exists down inside the method code itself, only to be discovered at runtime (if we fail the check).
An example of where RuntimeTypes are required is found in Activator.CreateInstance. You can take a generic type declaration (i.e., List<>) and use reflection to turn that into a concrete type declaration (i.e., List<object>):
Type genericListType = typeof(List<>); Type listOfObjectType = genericListType.MakeGenericType(typeof(object)); List<object> objectList = (List<object>)Activator.CreateInstance(listOfObjectType);
MakeGenericType will happily accept non-RuntimeTypes when creating the generic type, but as soon as you try to run Activator.CreateInstance() on it, you’ll get complaints that there are non-RuntimeTypes involved.
Why would you ever have a non-RuntimeType Type?
Since Type is an extensibility point in the system, you can derive from it and override members. One common reason you might want to do this is to influence the results of reflection; for example, adding or removing members that aren’t on the original type, or perhaps adding or removing attributes on members and/or parameters. Anything you can do with reflection, can be influenced by overriding methods or properties on Type.
In fact, the system provides just such a helper class for you: System.Reflection.TypeDelegator. The TypeDelegator class takes as its constructor parameter an existing Type object to wrap, and offers several virtual methods to be overriden (f.e., GetMethods) to provide hooks to influence the results of reflection. Anything you don’t specifically override is passed along to the wrapped Type.
In our example above, we used typeof(object) as the generic parameter type. The typeof operator is guaranteed to always return a RuntimeType, so this was a safe assumption. What if we'd been handed a Type instance, rather than getting one from the typeof operator?
How can I handle non-RuntimeTypes?
It's not safe to automatically assume that you've been handed a RuntimeType. If you write code that has to handle Type objects, and those objects come from any other method than the typeof operator, you have to consider the possibility that the Type you have isn’t a RuntimeType. You also need to be aware of the places where RuntimeTypes end up being required.
The Type class has a property named UnderlyingSystemType, which can be used to acquire the actual RuntimeType when you’re using a type delegator. Anywhere where you know you need a RuntimeType, you should use the UnderlyingSystemType property to retrieve it from any Type objects you’ve been given. If you call UnderlyingSystemType on a RuntimeType, it simply returns itself, since it’s already a RuntimeType.
In fact, Activator.CreateInstance does try to help you out as much as it can: when you pass it a Type object, it uses UnderlyingSystemType to ensure that it's talking to the RuntimeType rather than any type wrapper or delegator class. Of course, this only helps for top-level types; as we saw above, generics instantiated with non-RuntimeTypes will throw exceptions when you try to create them. There are other places in the framework where a non-RuntimeType will throw similar exceptions (f.e., setting a class’s base class when doing IL emit operations).
Unfortunately, the methods which require RuntimeType aren’t always obvious, because methods aren’t written with RuntimeType parameters. They often end up being checked at some point during runtime, to allow developers to pass around Type objects without being aware of their actual implementations. The best way to ensure that you properly handle non-RuntimeType Types is to write test code which passes instances of TypeDelegator into any of your methods which take a type. This will allow you to quickly determine when you’re calling methods which require RuntimeTypes.