Click here to Skip to main content
15,867,568 members
Articles / Mobile Apps
Article

Stateful Reflection

Rate me:
Please Sign up or sign in to vote.
3.50/5 (12 votes)
8 Jan 20056 min read 52.6K   301   17   4
Describes a series of classes to handle reflected members in a polymorphic, instance-specific manner.

Introduction

This article introduces a series of classes to perform reflection on properties in a stateful manner that retains the link between the reflected member and the instance that reflection was performed upon. It enables the reflected property and instance to be handled as one unit, allowing it to be passed around an application more easily.

In a nutshell, it's a series of classes that store MemberInfo (or equivalent) along with the instance being reflected.

This is particularly relevant in databinding (as you will see in my subsequent much-promised-but-still-sadly-unfinished article on 2-Way Databinding in ASP.NET), but there are other uses.

Background

Reflection in .NET is all very well, but what you reflect against is the class, and not the object. You obtain a handle to a MemberInfo representing method / field / property etc... on the class, but you still need to have a separate reference to an instance of the class to use the handle (unless it's a static member).

If your reflection and your use of the reflected member is within a few lines of each other, that's fine. However, what if you want to perform the location of the appropriate member in one module, but defer the use of that member to somewhere else? This separation of responsibility is a more OO way of doing things, but reflection in .NET forces you to pass around both the instance and the member-handle to do it. This is just a pain.

The itch I'm scratching is in recursive reflection operations - typically involving 'walking' a dot-path like this:

C#
"myObj.subObj.Value"

to retrieve the value of Value. You probably set up a loop to do it, and it probably looked a bit like this:

C#
public object RecursiveReflectGetGet(object obj, string path){
    string thisPart;
    string nextPart;
    Type type;
    do{
        type = obj.GetType();
        // Take the first part of the path off the path string
        string[] parts  =path.Split(new char[]{'.'},2);
        thisPart        =parts[0];
        nextPart        =(parts.Length>1) ? parts[1] : "";

        MemberInfo[] members =type.GetMember(thisPart);
        if (members.Length!=1)
            throw new 
              ApplicationException("Not handling this for this example");

        switch(members[0].MemberType){
            case MemberTypes.Property:{
                PropertyInfo prop = (PropertyInfo)members[0];
                // Indexed properties not handled either
                obj = prop.GetValue(obj, new object[0]);
            }break;
            case MemberTypes.Field:{
                FieldInfo field = (FieldInfo)members[0];
                obj = field.GetValue(obj);
            }break;
            default:
                throw new NotImplementedException();
        }
        // Advance the path string to the next dot-part (if present)
        path = nextPart;
    }while(path!="");

    return obj;
}

Each cycle of the loop takes the first dot-part off path, finds the relevant MemberInfo on obj, gets its value, and assigns it back to obj. The remainder of the path is also then re-assigned back onto path. At the end of the loop, we either cycle round again for the next level down the hierarchy, or there's no path left, so we're all done.

Now imagine for a moment that myObj.subObj.Value is string. We can get it back fine, but since it's a value type, any changes we make to it won't be made to the 'original', buried away in the object hierarchy. If we want to update the original, we're going to have to walk the dot-path all over again, but do a SetValue operation at the end. For the sake of simplicity, we'll reuse some of our existing code:

C#
public void RecursiveReflectSet(object obj, string path, object value){
    int i = path.LastIndexOf(".");
    if (i!=-1){
        // Just re-use RecursiveReflectGet to get the penultimate
        // part of the dot-path (if it's a complex expression)
        string thisPart = path.Substring(0,i);
        path = path.Substring(i+1);
        obj = RecursiveReflectGet(obj, thisPart);
    }

    MemberInfo[] members = obj.GetType().GetMember(path);
    if (members.Length!=1)
        throw new ApplicationException("Not handling this for this example");

    switch(members[0].MemberType){
        case MemberTypes.Property:{
            PropertyInfo prop = (PropertyInfo)members[0];
            // Indexed properties not handled
            prop.SetValue(obj, value, new object[0]);
        }break;
        case MemberTypes.Field:{
            FieldInfo field = (FieldInfo)members[0];
            field.SetValue(obj, value);
        }break;
        default:
            throw new NotImplementedException();
    }
  }
}

Seems a waste of cycles, right? We walk all the way down there to get Value, and then we have to walk all the way down to set it again. Worse, if Value had any metadata (attributes) on it, we've lost them. We've totally lost the context of where we got Value from. And this isn't just a problem for value types - if Value was a reference type but we wanted to change the reference rather than just work with it, then we're going to have to do the same work.

And that's without mentioning type conversion. What type is Value? If we RecursiveReflectGet before we RecursiveReflectSet, we know Value's content, and therefore its Type since we've got a reference to it. But if we just want to do a RecursiveReflectSet straight off, we've got to do a get first just to get the type, and then a set. And that's all assuming that Value is not null. The only place that really knows Value's type is the last bit of RecursiveReflectionSet, so we could do our type conversion in there. But that's the wrong way round: we want to do the type conversion first, in the caller, since the client may have specific type conversion rules to follow that depends on the context of what it did with Value in the first place, and that's not possible here (you could get round this with a delegate, but it'd be messy).

Ditto metadata. Say we've marked Value with various attributes that allow clients to discover valid scope for its value, like (fictitious example) a MaxLengthAttribute(40), DefaultValue(""), or a TypeConverter. We've lost all that too.

That switch{} statement's got 'refactor for polymorphism' written all over it too.

All of these problems evaporate if we return our final obj and MemberInfo to the client. The client can then get the value, play with it and then set it without having to re-parse the dot-path, and with knowledge of the Type, any custom attributes, this works. But that's a pain: we've got two things to return (I dislike output parameters), and now it's the client that's having to switch{} for each MemberInfo subtype, which makes the code harder to work with.

Enter IPropertyInstance.

The Solution: IPropertyInstance

Predictably, IPropertyInstance combines a reflected 'property' with the instance that the property applies to. I say 'property', but the interface is just supposed to cover any property-like behavior, and there are classes to cover both PropertyInfo and FieldInfo as well as PropertyDescriptor and indexed properties. It's just a generic interface to let you get and set something, and because it's nicely parceled up, you can pass them around with impunity, without exposing clients to the internal specifics of what you've been reflecting against:

C#
public interface IMemberInstance
{
    /// <summary>
    /// Retrieve the object instance that this IMemberInstance manipulates
    /// </summary>
    object Instance{ get; }

    /// <summary>
    /// Retrieves the name of the instance member
    /// </summary>
    string Name { get; }

    /// <summary>
    /// Gets/sets the value of <c>Name</c> on <c>Instance</c>
    /// </summary>
    object Value { get;set; }

    /// <summary>
    /// Retrieves the type of the member
    /// </summary>
    Type Type { get; }

    /// <summary>
    /// Retrieves an appropriate TypeConverter
    /// for the property, or null if none retrieved
    /// </summary>
    TypeConverter Converter { get; }
}

There are various implementations of IPropertyInstance included in the code, including PropertyInfoInstance (for manipulating properties), FieldInfoInstance (for fields) and IndexedPropertyInfoInstance (for indexed properties). Most of these include a constructor that can be used if you already have a handle on the relevant PropertyInfo / PropertyDescriptor etc... and a static GetInstance(object instance, string memberName) method which will create a IMemberInstance from an object and a (string) member name. This saves you from having to do your own reflection, plus the static method on the abstract class MemberInfoInstance will return a FieldInfoInstance, PropertyInfoInstance or IndexedPropertyInfoInstance, automatically determining the type of the member.

(There're a few more, including PropertyDescriptorInstance, which I'll cover in the 2WayDataBinding article).

Using the code

Typically, just use one of the static methods to get your IMemberInstance, and then just get/set the Value:

C#
IMemberInstance member =MemberInfoInstance.GetInstance(obj, "AccountBalance");
decimal value =(decimal)member.Value;
// Do some calculations that update value
    member.Value =value;

Of course, the cast is potentially problematic, so perhaps:

C#
IMemberInstance info =MemberInfoInstance.GetInstance(obj, "AccountBalance");
if (info.HasConverter && info.Converter.CanConvertFrom(typeof(decimal))){
    decimal typedValue =(decimal)info.Converter.ConvertFrom(dataMember.Value);
    // do calculations etc...
    info.Value =info.Converter.ConvertTo(typedValue, info.Type);
}

Now these examples are pretty trivial. I've used this as the core of my TwoWayDataBinder class, but what you use it for is up to you. Clearly, it's only really useful in scenarios where the use of a member is determined from an expression evaluated at runtime, like in databinding or RAD tools.

Incidentally, this type of type-conversion is exactly the mechanism that the Visual Studio designer uses to convert the strings you enter in the property grid into typed values on the controls being designed. Normally, it's setup to convert things to/from strings, so you could enter 'Mortgage' into the AccountType field of an object being designed, and the type-conversion code would - say - convert that into the AccountType.Mortgage enum (or even an instance of the 'Mortgage' class). There's no reason to restrict this to the designer however - it's just a general purpose mechanism for specifying rules for converting objects into other types of objects (as opposed to IConvertable, which allows objects to convert into the built-in value types like Int32 and DateTime).

If you haven't got NUnit installed, you'll have to drop the project reference to NUnit before you can get it to build. If you have got NUnit and you want to run the unit tests, add a TEST compilation directive to the DEBUG build.

Competition

List as many potential uses of this technique as you can. The prize is being smug.

History

  • 9/1/05 - First version of article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Australia Australia
There's some kinda mutex between money and the time to enjoy it, and it's called work.

Comments and Discussions

 
General2WayDataBindingArticle is up Pin
piers720-Sep-05 15:51
piers720-Sep-05 15:51 
GeneralCheck the base class, too Pin
Kreeg20-Sep-05 5:31
Kreeg20-Sep-05 5:31 
AnswerRe: Check the base class, too Pin
piers720-Sep-05 15:48
piers720-Sep-05 15:48 
Generalminor but important error Pin
Anonymous16-Jan-05 0:01
Anonymous16-Jan-05 0:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.