Reflection for WPF Rockstars

Reflection is a very powerful tool for building code that is highly customizable at runtime, making it very reusable. If you’re going to make an application that is primarily loose XAML, you will want to make whatever code you do write as flexible as possible, and to do so, it helps to have a firm grasp of reflection.

Before digging into it, I want to dispel one myth. Reflection is not slow. Database access is slow. Network calls are slow. Rendering the user interface is slow. Throwing an exception is slow. People seem perfectly comfortable to make network and database calls or execute business logic in event handlers on a (very busy) UI thread, but won’t use reflection because somebody said told them it is going to slow down their application. Using reflection for late-binding to invoke a method or property is “slower” than the early-bound compiled method call, but it is many orders of magnitude faster than waiting on a database or a UI rendering, and it gives you much more configurable code, which usually also means less code is necessary to do more things. If there is less code, then it is easier to optimize and maintain your application. Do not be afraid of reflection.

For a complete reflection tutorial, O’Reilly was nice enough to provide Chapter 18, Attributes and Reflection from Programming C# by Jesse Liberty. For WPF, a very common use for reflection is converting a string in a XAML attribute to an object using IValueConverter. An IValueConverter can be used in any Binding to convert an object from one type to another whenever the binding occurs. The conversion will also occur whenever the source or target of the binding is updated.

System.Windows.Data.IValueConverter defines two methods, Convert and ConvertBack. The tricky thing for converters is that you get an object which is the value to convert and a target type, which is the type you want to create, along with a parameter, which can be anything else:

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture);

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture);

For example, suppose you want to have a single TextBox where someone can enter a comma delimited list of stock symbols and bind that to a List to send to a web service. Without reflection, you could write a converter that will assume the value is a comma delimited string and creates a List out of it.

List<StockSymbol> list = new List<StockSymbol>();
string stockSymbols = value as string;
if (!String.IsNullOrEmpty(stockSymbols))
{
    string[] symbols = stockSymbols.Split(',');
    for (int i = 0; i < symbols.Length; i++)
    {
        StockSymbol stockSymbol = new StockSymbol();
        stockSymbol.Code = symbols[i];
        list.Add(stockSymbol);
    }
}
return list;

Easy enough, no reflection needed, and every time you need to do something like this, just write a new converter. Unless of course, you don’t want to maintain a dozen or so converters. Instead, a very reusable converter for this might be a DelimitedStringToListConverter which uses reflection to inspect the target type, determine what types are expected in the collection, and create them automatically.

Type[] typeArguments = targetType.GetGenericArguments();
Type listItemType = null;
IList list = null;
if (typeArguments.Length == 1)
{
    listItemType = typeArguments[0];
    Type listType = typeof(List<>).MakeGenericType(listItemType);
    list = (IList)Activator.CreateInstance(listType);
}
else
{
    // If there aren't any type arguments, we won't know what type to create.  This is a 
    // good place for throwing an Exception.
    throw new Exception("DelimitedStringToListConverter must be used with a Generic List");
}

// We know the type of item to add to the list, now we just need to know what property 
// to set from the delimited string.  Let's use the converter parameter for this.
string itemPropertyName = parameter as String;
PropertyInfo propertyInfo = listItemType.GetProperty(itemPropertyName);

string delimitedText = value as string;
if (!String.IsNullOrEmpty(delimitedText))
{
    string[] splitItems = delimitedText.Split(',');
    for (int i = 0; i < splitItems.Length; i++)
    {
        object item = Activator.CreateInstance(listItemType);
        propertyInfo.SetValue(item, splitItems[i], null);
        list.Add(item);
    }
}
return list;

To use this converter in a binding between a TextBox.Text property and a List StaticResource, you can use markup similar to the following:

<TextBox Text="{Binding Source={StaticResource stockSymbols}, Converter={StaticResource textToListConverter}, ConverterParameter='Symbol'}" />

This converter becomes much more reusable since it can work with any sort of generic list. I’ll go over the tricky parts:

To get the list of type arguments (the T in List<T>), you use GetGenericArguments(), which will return an array of types used to define this generic. In the case of a generic List, there should only be one.

Next, to create the List, you could just use Activator.CreateInstance(targetType), but for illustrative purposes, an example of creating a generic List<T> given just the list item type requires you first build the generic type definition:

Type listType = typeof(List).MakeGenericType(listItemType);

Once you have the generic type, you can simply use the Activator:

list = (IList)Activator.CreateInstance(listType);

The next trick is determining what property to set on each list item from the delimited string. A Binding has a ConverterParameter property where you can set the parameter to pass to the converter, and in this case, it is used to specify the name of the property to set. You should get the PropertyInfo for the list item type just once before looping through the delimited string since that is the “slow” part of reflection.

string itemPropertyName = parameter as String;
PropertyInfo propertyInfo = listItemType.GetProperty(itemPropertyName);

Then after creating an instance for each string in the delimited string, you use the PropertyInfo to set the property value on each instance:

propertyInfo.SetValue(item, splitItems[i], null);

Value converters are essential to WPF because they allow you to declaratively convert between XAML and CLR types. There is nothing too complicated about reflection, but it takes some time to get comfortable inspecting and using type information. Because XAML allows you so much runtime configuration of your user interface, using reflection will provide you with significant reuse of logic for converters and other WPF extensions you build into your WPF applications.

Code Listing for the DelimitedStringToListConverter

Advertisements
  1. December 28, 2009 at 1:21 pm

    What if it is not an IList but pre-historic IList? How do you propose to handle that?

    • loosexaml
      December 28, 2009 at 7:03 pm

      What is an example of a “pre-historic” IList? Do you mean a System.Collections.IList such as an ArrayList? If that’s the case, you wouldn’t be able to determine the list item type from the targetType parameter in the Convert() and ConvertBack() methods since it wouldn’t have any generic parameters. You would need to add a public property on the type converter where you could set this. Then when you create the instance of your type converter in your Resources collection, you’ll have to specify the list item type by setting that property (in XAML), with the {x:Type} notation. Your Convert and ConvertBack methods can simply refer to that property on the type converter. Make sense?

  2. December 29, 2009 at 11:24 am

    Precisely. I am writing a XAML parser and this is one problem that I was facing. I was able to add items to a generic IList or ICollection but was not able to do it in case of the non generic IList and ICollection (pre-historic). Now I have the type specified in the XAML itself to solve that problem. Your comments helped. Thanks…

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: