Deep reflection of properties: PropertyReflector
In our current project we want to fill a grid control with data that is comming from value objects. I can hear you think: “Easy, just use standard .NET data binding!” True, but the grid control that we are currently using does not have full databinding support, and using another control is not an option at this moment.
Rather than implementing some logic that will map the contents of each value object type to a row in our grid control, I chose to create a utility class (named PropertyReflector) that allows us to read and write the properties of our value objects through reflection. Since we need to access properties of value-objects within other value-objects, the utility class must also be able to do deep reflection: getting and setting values of nested objects.
Read the rest of this article for examples and source code!
[03-Feb-2007] Update: uploaded a new version of the PropertyReflector source
[03-Feb-2007] Update: uploaded the nunit tests for PropertyReflector
To make this work, our value objects must meet the following requirements:
- Declare public get/set property accessors for fields that should be displayed in/filled from the grid
- Have a default constructor, so instances of nested objects can be created if necessary when setting properties on them
To demonstrate how the PropertyReflector utility class works, we’ll use the following test object:
public class TestObject { private TestObject parent; private string name; public TestObject() {} public TestObject(string name) { this.name = name; } public TestObject(string name, TestObject parent) { this.parent = parent; this.name = name; } public TestObject Parent { get { return parent; } set { parent = value; } } public string Name { get { return name; } set { name = value; } } }
As you can see, the TestObject has a name and a nested TestObject representing its parent. It has public property accessors for both fields, a default constructor and a constructor with parameters for ease of use.
We can now use the PropertyReflector utility class to get and set properties on our TestObject, like this:
TestObject to = new TestObject( "Son", new TestObject( "Father", new TestObject("Grandfather") ) ); PropertyReflector pr = new PropertyReflector(); object grandFathersName = pr.GetValue(to, "Parent.Parent.Name"); Console.Out.WriteLine("grandFathersName = {0}", grandFathersName ); to = new TestObject("Son"); pr.SetValue(to, "Parent.Parent.Name", "Another Grandfather"); object anotherGrandfathersName = pr.GetValue(to, "Parent.Parent.Name"); Console.Out.WriteLine("(Son).Parent.Parent.Name = {0}", anotherGrandfathersName);
You can see some nested TestObjects being created: Son > Father > Grandfather. In line 10, the Name property of the deepest TestObject is obtained. Notice that the property names are separated by dots to indicate a new level in the hierarchy: “Parent.Parent.Name”.
To demonstrate the ability of instantiating objects for nested properties that have null references, a new TestObject instance is created on line 16, after which the name of its grandfather is set (lines 17 & 18) without creating father or grandfather objects first.
Running the code above will generate the following output:
grandfathersName = Grandfather (Son).Parent.Parent.Name = Another Grandfather
The use of the PropertyReflector class is not limited to value objects, you could use it to do deep reflection on any type of object that declares public properties. To demonstrate this, we’ll get the value for the Length property of a String object:
PropertyReflector pr = new PropertyReflector(); string a = "1234"; object length = pr.GetValue(a, "Length"); Console.Out.WriteLine("length = {0}", length);
The output for this code will be:
length = 4Although this is nothing exotic, using this utility class we have been able to describe our grid and its contents completely through xml. No extra coding is needed in our project to create a grid for a new business concept, we simply specify the layout settings and the name of the associated property for each column.
I also added caching of the PropertyInfo and ConstructorInfo instances to speed up the reflection process. All internal access to these caches has been made thread-safe.
Here is a link to the source code for the PropertyReflector class. I also have some NUnit tests for it that test how it behaves with inheritance and dynamic proxies generated at runtime.
Feel free to use and change it, but if you make improvements to it, I would appreciate it if you’d send them back to me :).
Tuesday 11 Jul 2006 | Guy Mahieu | .net(t) , c#(t) , reflection(t)
Great work!
Hi!
Great work I think, too. But what about if you have hierarchy of objects that contains collections of objects below your root object? What about relations between objects? Do I need to implement this myself?
There is no support for collection indexers at this time, but it might be interesting to add this. You could of course just get the collection through GetValue(object, “MyCollectionProperty”) and change its contents in code.
I don’t know what you mean by relations, but it is just a simple utility I made to allow me to specify in xml which values to display for certain objects, so it just gets and sets property values on objects.
Thanks for your fast respond. It could be interesting trying to implement collection support. And of cource, my fault, you do have relationship between objects through the object hierarchy. Thanks anyway
Man, what a great work! It’s what I’m needing to get relationship properties to render a dropdownlist using reflection.
Thanks
Great work.. Keep it up.
Thanks and Regards,
Rajaraman. B.
Looks very cool. Yet I#D like to see a functionality like “Get actual string name of a property” (as requested in http://www.devnewsgroups.net/group/microsoft.public.dotnet.framework.windowsforms/topic63311.aspx)
The point is that referring to an object by its name string will not survive refactoring operations - the same idea is behind linq i suppose.
Instead of
Binding b = new Binding(”Text”, Obj, “MyProp”, true);
I’d like to do something like:
Binding b = new Binding(”Text”, Obj, GetPropName(Obj.MyProp), true);
Do you have any idea how to accomplish that?
Thanks
Well, I use resharper which also looks inside strings when refactoring. Therefore this has never been a real issue for me :)
The whole point for this class however was to be able to make the reflected properties configurable through xml files. Therefore a method GetPropName(obj.prop) would defeat the purpose since I did not know the properties/objects at compile-time.
Guy,
This is exactly what I’ve been looking for in the past day. We have some very complicated classes that we wish to pass a property list to. This property list may or may not have classes as properties that we’ll need to pull values from. Your utility class will work perfectly.
I would be interested in seeing the collection portion implemented, but think I can do this myself if it’s not on the forefront. If there is an update of such, I’d appreciate an e-mail or some type of notification.
Thanks very much for saving me a ton of work :).
Davis
Davis,
Glad to hear you find it useful!
I do not plan to make any additions to it myself in the near future, but feel free to use/alter the code as you wish.
Cheers,
Guy
This Code Does Not Work.The GetValue Function Gets The Embedded Object And Sets A Value To It,But The Problem Is That The Main Object Which Has Been Passed Does Not Change.May Be It’s Because The GetValue Is Working ByValue Here And Not ByReference.I Checked This On Something Like BOX.BOY.Name
I’m not sure I completely understand what you mean.
Could you post a code sample with the actual result and the expected result?
May Be I Was Unclear.The Problem Is That You Have Supposed That PropertyInfo.GetValue Will Return An Object Which Has A Reference To The Main Object.This Is True IF All Properties Are Objects.But When Your Try TO Do A GetValue On A Property Which Is A Primative Type,The PropertyInfo.GetValue Will Return A Copy Of The Property And Not A Reference.
For Instance,We Want To Set A Value To “BOX.BOY.Name” In Which BOX and BOY and Name Are Objects Everything Works Fine.But When You Try Something Like “BOX.BOY.ID” In Which The ID Is Of Type Integer(Primative) When A GetValue Is Done On The ID Property,A Copy Of ID Will Be Returned Back Not A Reference To The Real ID Field.
So The Solution Only Works For Fields Which Are Objects And Not Primative Types Such As Integers Or Boolean Values.
Still, I think you are missing the point of what this class is supposed to do. You can get and set properties on classes using strings containing property names. I don’t understand why you would expect an integer (or any immutables or value types for that matter) obtained by a Get to be a reference to an integer in the object. Just do a Set after updating the returned value and you’ll be fine.
Hi Guy,
Great post - I managed to extend this to support indexed/array properties by checking the type.IsArray property.
You can then cast to IList to use a zero based index (or whichever you need), e.g. ((IList)target)[0].
[...] is your friend on this. Something you might be interested on is a very nice example of code on http://blog.guymahieu.com/?p=9. It is a class to encapsulate all the logic you need. But going with a simple example, it would [...]
Thanks bud. You just concluded with a great note a day full of frustrations. Your code worked the first time for what I needed to do: Bind a grid to nested object properties. Bravo.
Come to think of it, it’s pretty frustrating that web apps can do this easily with the DataBinder while windows forms need to resort to reflection…
Cheers.
Be name Khoda
Hi Guy!
First of all I should thank you for you effort on writing this class.
I added ‘ProperyReflector’ class to my website solution and everything got ok.
But I have a small problem: I have a page that there are two TextBoxes and a Label on it. In textbox1 I enter the desired property and in the second the value of the property. After I click on a button on the page the value in the textbox2 is set for the property (in the textbox1) of the label. For a property like ‘Width’ I encounter an error that says that I can not convert a value of string to system.controls.unit
As I noticed your example all properties are string and you didn’t mention how to send other types variables to the SetValue method.
@Mostafa: SetValue takes any object, so in this case you should probably not be passing the entered String into it, but convert it to a Unit first.
Thank you Guy and congratulations for your very clear explanation.
This was very helpful thanks for the great article. I made some modifications to the GetValue Method so it can handle generic Lists:
Notice it uses an extra method to test if a Type is a Generic List:
public static bool IsGenericList(Type type)
{
foreach (Type @interface in type.GetInterfaces())
{
if (@interface.IsGenericType)
{
if (@interface.GetGenericTypeDefinition() == typeof(ICollection))
{
return true;
}
}
}
return false;
}
public object GetValue(object target, string propertyName)
{
if (propertyName.IndexOf(PropertyNameSeparator) > -1)
{
int index = -1;
string[] propertyList = propertyName.Split(PropertyNameSeparator);
for (int i = 0; i = 0)
{
IList list = (IList) target;
if (index < list.Count)
{
//Get the corresponding item in the list
target = list[index];
}
}
if (target == null)
{
return null;
}
}
return target;
}
else
{
return GetValueImpl(target, propertyName);
}
}
An example of the usage:
public class Store
{
public Store()
{
StringInstruments = new PluckedStrings();
}
public PluckedStrings StringInstruments { get; set; }
}
public class PluckedStrings
{
// Fields
private List guitars = new List();
// Properties
public List Guitars
{
get { return this.guitars; }
set { this.guitars = value; }
}
}
public class Guitar
{
public string Brand { get; set; }
}
And Finally we test it:
Store mystore = new Store();
Guitar lucille = new Guitar();
lucille.Brand = “Gibson”;
mystore.StringInstruments.Guitars.Add(lucille);
//Get the Brand value for Guitar Item at position 0, or any other index
object brand = pr.GetValue(mystore, “StringInstruments.Guitars[0].Brand”);
I hope this helps and thanks again for the post,
Adolfo
Hi Guy,
The modified version of GetValue method I just posted got chopped off it should be:
public object GetValue(object target, string propertyName)
{
if (propertyName.IndexOf(PropertyNameSeparator) > -1)
{
int index = -1;
string[] propertyList = propertyName.Split(PropertyNameSeparator);
for (int i = 0; i = 0)
{
var List = (IList) target;
if (index < List.Count)
{
target = List[index];
}
}
if (target == null)
{
return null;
}
}
return target;
}
else
{
return GetValueImpl(target, propertyName);
}
}
Regards,
Adolfo
Thanks for sharing these tips. It is very helpful for us.
Hi Adolfo,
Do you have complete source available somewhere? I have trouble getting all code snippets glued together.
Thanks,
JN
I’ve modified a little bit the code to allow indexed access to IList or IEnumerable properties.
Use example:
PropertyReflector pr = new PropertyReflector();
var anon = new { Colors = new Dictionary() { {100, Color.Red}, {200, Color.Blue} }, SelectedColor = 100};
object o1 = pr.GetValue(anon, “Colors[0].Key”);
object o2 = pr.GetValue(anon, “Colors[1].Value”);
object o3 = pr.GetValue(anon, “SelectedColor”);
The Code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
// Original from: http://blog.guymahieu.com/wp-content/uploads/2007/02/propertyreflector.cs
// Modified to allow IList & IEnumerable for indexed access (ie: MyCollection[1].Name)
namespace Core
{
public class PropertyReflector
{
private const char PropertyNameSeparator = ‘.’;
private static readonly object[] NoParams = new object[0];
private static readonly Type[] NoTypeParams = new Type[0];
private static readonly Regex RegExIndex = new Regex(@”\[(?'index'\d+)\]“);
private IDictionary propertyCache = new Dictionary();
private IDictionary constructorCache = new Dictionary();
public Type GetType(Type targetType, string propertyName)
{
if (propertyName.IndexOf(PropertyNameSeparator) > -1)
{
string[] propertyList = propertyName.Split(PropertyNameSeparator);
for (int i = 0; i -1)
{
string[] propertyList = propertyName.Split(PropertyNameSeparator);
for (int i = 0; i -1)
{
object originalTarget = target;
string[] propertyList = propertyName.Split(PropertyNameSeparator);
for (int i = 0; i < propertyList.Length - 1; i++)
{
propertyName = propertyList[i];
target = GetValueImpl(target, propertyName);
if (target == null)
{
string currentFullPropertyNameString = GetPropertyNameString(propertyList, i);
target = Construct(GetType(originalTarget.GetType(), currentFullPropertyNameString));
SetValue(originalTarget, currentFullPropertyNameString, target);
}
}
propertyName = propertyList[propertyList.Length - 1];
}
SetValueImpl(target, propertyName, value);
}
private static string GetPropertyNameString(string[] propertyList, int level)
{
StringBuilder currentFullPropertyName = new StringBuilder();
for (int j = 0; j 0)
{
currentFullPropertyName.Append(PropertyNameSeparator);
}
currentFullPropertyName.Append(propertyList[j]);
}
return currentFullPropertyName.ToString();
}
private Type GetTypeImpl(Type targetType, string propertyName)
{
return GetPropertyInfo(targetType, propertyName).PropertyType;
}
private object GetValueImpl(object target, string propertyName)
{
Match match = RegExIndex.Match(propertyName);
int index = -1;
if (match.Success)
{
// We have an index, get it and drop the index part “[i]” to get the List property
index = int.Parse(match.Groups["index"].Value);
propertyName = propertyName.Replace(match.Value, string.Empty);
}
PropertyInfo propInfo = GetPropertyInfo(target.GetType(), propertyName);
if (index >= 0 && typeof(IEnumerable).IsAssignableFrom(propInfo.PropertyType))
{
return GetValueForIndexedItem(target, propInfo, index);
}
return propInfo.GetValue(target, NoParams);
}
private object GetValueForIndexedItem(object target, PropertyInfo propInfo, int index)
{
if (typeof(IList).IsAssignableFrom(propInfo.PropertyType))
{
return ((IList)propInfo.GetValue(target, NoParams))[index];
}
else if (typeof(IEnumerable).IsAssignableFrom(propInfo.PropertyType))
{
int i = 0;
foreach (object o in ((IEnumerable)propInfo.GetValue(target, NoParams)))
{
if (index == i)
{
return o;
}
i++;
}
throw new IndexOutOfRangeException(string.Format(”Property ‘{0}’ index ‘{1}’ out of range”, propInfo.Name, index));
}
else
{
throw new ArgumentException(string.Format(”Property ‘{0}’ must implement IList or IEnumerable in order to access an indexed value”, propInfo.Name));
}
}
private void SetValueImpl(object target, string propertyName, object value)
{
GetPropertyInfo(target.GetType(), propertyName).SetValue(target, value, NoParams);
}
private PropertyInfo GetPropertyInfo(Type type, string propertyName)
{
PropertyInfoCache propertyInfoCache = GetPropertyInfoCache(type);
if (!propertyInfoCache.ContainsKey(propertyName))
{
PropertyInfo propertyInfo = GetBestMatchingProperty(propertyName, type);
if (propertyInfo == null)
{
throw new ArgumentException(string.Format(”Unable to find public property named {0} on type {1}”, propertyName, type.FullName), propertyName);
}
propertyInfoCache.Add(propertyName, propertyInfo);
}
return propertyInfoCache[propertyName];
}
private static PropertyInfo GetBestMatchingProperty(string propertyName, Type type)
{
PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
PropertyInfo bestMatch = null;
int bestMatchDistance = int.MaxValue;
for (int i = 0; i 0 && distance < bestMatchDistance)
{
bestMatch = info;
bestMatchDistance = distance;
}
}
}
return bestMatch;
}
private static int CalculateDistance(Type targetObjectType, Type baseType)
{
if (!baseType.IsInterface)
{
Type currType = targetObjectType;
int level = 0;
while (currType != null)
{
if (baseType == currType)
{
return level;
}
currType = currType.BaseType;
level++;
}
}
return -1;
}
private PropertyInfoCache GetPropertyInfoCache(Type type)
{
if (!propertyCache.ContainsKey(type))
{
lock (this)
{
if (!propertyCache.ContainsKey(type))
{
propertyCache.Add(type, new PropertyInfoCache());
}
}
}
return propertyCache[type];
}
private object Construct(Type type)
{
if (!constructorCache.ContainsKey(type))
{
lock (this)
{
if (!constructorCache.ContainsKey(type))
{
ConstructorInfo constructorInfo = type.GetConstructor(NoTypeParams);
if (constructorInfo == null)
{
throw new Exception(string.Format(”Unable to construct instance, no parameterless constructor found in type {0}”, type.FullName));
}
constructorCache.Add(type, constructorInfo);
}
}
}
return constructorCache[type].Invoke(NoParams);
}
}
internal class PropertyInfoCache
{
private IDictionary propertyInfoCache;
public PropertyInfoCache()
{
propertyInfoCache = new Dictionary();
}
public bool ContainsKey(string key)
{
return propertyInfoCache.ContainsKey(key);
}
public void Add(string key, PropertyInfo value)
{
propertyInfoCache.Add(key, value);
}
public PropertyInfo this[string key]
{
get { return propertyInfoCache[key]; }
set { propertyInfoCache[key] = value; }
}
}
}
Hope it helps.
Federico D. Colombo