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:
"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:
public object RecursiveReflectGetGet(object obj, string path){
string thisPart;
string nextPart;
Type type;
do{
type = obj.GetType();
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];
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();
}
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:
public void RecursiveReflectSet(object obj, string path, object value){
int i = path.LastIndexOf(".");
if (i!=-1){
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];
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:
public interface IMemberInstance
{
object Instance{ get; }
string Name { get; }
object Value { get;set; }
Type Type { get; }
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
:
IMemberInstance member =MemberInfoInstance.GetInstance(obj, "AccountBalance");
decimal value =(decimal)member.Value;
member.Value =value;
Of course, the cast is potentially problematic, so perhaps:
IMemberInstance info =MemberInfoInstance.GetInstance(obj, "AccountBalance");
if (info.HasConverter && info.Converter.CanConvertFrom(typeof(decimal))){
decimal typedValue =(decimal)info.Converter.ConvertFrom(dataMember.Value);
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.