Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET 4.5 CustomReflectionContext: what is it useful for?

What's New in the .NET Framework 4.5 Developer Preview mentions

Ability to customize a reflection context to override default reflection behavior through the CustomReflectionContext class.

What is the purpose of the ReflectionContext? MSDN is not quite clear on the subject.

like image 345
Andrey Shchekin Avatar asked Feb 29 '12 21:02

Andrey Shchekin


1 Answers

In the past with .NET, there has been tension between wanting to be able to automate certain features through reflection, and being able to customize them. For example, take the Properties panel in Visual Studio - in scenarios where this shows some .NET type (e.g., a control on a design surface), it can automatically discover and display every public property that the type defines.

Using reflection for this sort of type-driven behaviour is useful because it means that every property will show up without the developer of the control needing to do anything. But it presents a problem: what if you want to customize things, e.g. defining categorization or a custom editing UI for a particular property?

The classic solution in .NET is to slap a bunch of custom attributes onto the relevant members. However, one problem with that is that it can mean the parts of your code that do a meaningful job at runtime end up depending on classes that only do anything at design time - relying on attributes prevents you from separating out the runtime and design time aspects. Do you really want to ship the code for a custom designer UI for VS's properties panel as part of a control library that will end up on end user machines?

Another problem is that in some situations you may want to decide dynamically what 'properties' you present. One of the oldest examples of this (dating back to .NET 1.0) was putting a DataSet in some sort of grid control (either client-side or web). With a strongly-typed dataset, reflection might be an appropriate way for the grid to discover which properties the source provides, but DataSet could also be used dynamically, so you need a way for the data grid to ask at runtime what columns to display.

(One answer to this is: design your UI properly! Generating grids directly like this leads to horrible user experiences. However, a lot of people want to do it the lazy way, whether it's a good idea or not...)

So you then have a situation where sometimes you want reflection-driven behaviour, but sometimes you want to be able to take total control at runtime.

Various ad hoc solutions emerged for this. You have the whole TypeDescriptor and PropertyDescriptor family of types, which provide a sort of virtualizable view on top of reflection. By default, this would just pass everything straight through from reflection but types have the opportunity to opt into providing custom descriptors at runtime, enabling them to modify or even completely replace how they look. ICustomTypeDescriptor is part of that world.

That provides one solution to the issue of wanting reflection-driven behaviour by default with the option to provide runtime-driven behaviour if you want it. But it doesn't solve the problem where you only want to do this at design time, and you don't want to have to ship that code as part of your runtime redistributables.

So a few years ago, Visual Studio introduced its own ad hoc mechanisms for augmenting type information at design time. There's a bunch of convention-driven behaviour in which Visual Studio will automatically discover the design-time components that related to particular runtime components, enabling you to customize the design experience without needing to bake the relevant code into your redistributables. Blend also uses this mechanism, although with some tweaks, making it possible to provide different designer pieces for VS and Blend.

Of course, none of that's visible through the normal reflection APIs - VS and Blend have a wrapper layer that sits on top of reflection to make this all work.

So now we've got two virtualization layers that can pass through to reflection, or can augment what comes out of reflection...

It looks like in .NET 4.5, the CLR team decided that since various groups were already doing this sort of thing, and other groups wanted to do more of it (the MEF team had similar requirements for reflection-driven-with-optional-runtime-augmentation behaviour), this was exactly the sort of thing that should be built into the runtime.

The new model seems to be this: the ReflectionContext base class is an abstract API through which you can get a virtualized version of the reflection API. It's deceptively simple, because one of the main ideas is that you no longer need specialized APIs like the type descriptor system if your only goal is to get a virtualizable wrapper on top of reflection - reflection is now virtualizable out of the box. So you can write this sort of thing

public static void ShowAllAttributes(Type t)
{
    foreach (Attribute attr in t.GetCustomAttributes(true))
    {
        Console.WriteLine(attr);
    }
}

Now you've always been able to write that, but prior to .NET 4.5, code like this would always be working against 'real' type information because it uses Reflection. But thanks to reflection contexts, it's now possible to provide this with a virtualized Type. So consider this very boring type:

class NoRealAttributes
{
}

If you just pass typeof(NoRealAttributes) to my ShowAllAttributes method, it will print out nothing. But I can write a (somewhat contrived) custom reflection context:

class MyReflectionContext : CustomReflectionContext
{
    protected override IEnumerable<object> GetCustomAttributes(MemberInfo member, IEnumerable<object> declaredAttributes)
    {
        if (member == typeof(NoRealAttributes))
        {
            return new[] { new DefaultMemberAttribute("Foo") };
        }
        else
        {
            return base.GetCustomAttributes(member, declaredAttributes);
        }
    }
}

(By the way, I think the distinction between CustomReflectionContext and its base, ReflectionContext is that the latter defines the API for a virtualizable reflection context, while CustomReflectionContext adds some helpers to make it easier for you to implement such a thing.) And now I can use that to provide a virtualized version of the Type for my class:

var ctx = new MyReflectionContext();
Type mapped = ctx.MapType(typeof(NoRealAttributes).GetTypeInfo());
ShowAllAttributes(mapped);

In this code, mapped still refers to a Type object, so anything that knows how to use the reflection API will be able to work with it, but it will now report the presence of an attribute that isn't actually there. Of course, Type is abstract, so we always have something that's derived from that, and if you call mapped.GetType() you'll see that it's actually a System.Reflection.Context.Custom.CustomType rather than the System.RuntimeType you'd normally see. And that CustomType object belongs to my custom context, so any other reflection API objects you get hold of through it (e.g., if you wrote mapped.Assembly.GetTypes()) you'd also get customized objects that go through my custom context, which would have the opportunity to modify anything else that comes out.

So code can navigate through the type system using the customized Type object. Even though such code is using the ordinary basic reflection API, I now have the opportunity to customize anything that comes out of that if I see fit.

You only get this virtualized view if you ask for it. For example, MEF in .NET 4.5 looks for a custom attribute specifying that it should use a user-supplied custom reflection context, but will fall back to ordinary reflection otherwise. (And in the case of my ShowAllAttributes method, it uses whatever Type object I choose to pass in - it doesn't know if it's getting a virtualized or a 'real' type object.)

So in short, this means you no longer need ad hoc wrappers around the reflection API if you want virtualized type information.

like image 191
Ian Griffiths Avatar answered Nov 13 '22 21:11

Ian Griffiths