Reflection is a hugely useful technology: it allows you to get inside objects and get their intimate details, modify their values and even rewrite part of their code.
Today I wanted to build a simple treeview control that would display the hierarchical structure of an arbitrary object by going through its properties and building a tree-like structure. Useful to visualise the internals of an object and see changes at runtime.
Using PropertyInfo you get enough information about a particular property to decide if you want to display it or recurse into it if it’s a complex type that contain other properties.
The only big issue I faced was when dealing with indexers.
###The issue with Indexers###
Indexers are defined in code like that:
~~~~brush:csharp
using System;
using System.Collections;
public class DayPlanner
{
// We store our indexer values in here
Hashtable _meetings = new System.Collections.Hashtable();
// First indexer
public string this[DateTime date] {
get {
return _meetings[date] as string;
}
set {
_meetings[date] = value;
}
}
// Second indexer, overloads the first
public string this[string datestr] {
get {
return this[DateTime.Parse(datestr)] as string;
}
set {
this[DateTime.Parse(datestr)] = value;
}
}
}
~~~~
We’ve defined an overloaded indexer: one takes a `DateTime`, the other just a string representation of a date.
We could use them like that for instance:
~~~~brush:csharp
DayPlanner myDay = new DayPlanner();
myDay[DateTime.Parse(“2006/02/03”)] = “Lunch”;
…
string whatNow = myDay[“2006/06/26”];
Now, say we want to print all property names and their values for an arbitrary object
Let’s try on our `myDay` intance:
foreach (PropertyInfo pi in picoll) {
object val;
val = pi.GetValue(myDay, null);
Debug.WriteLine(pi.Name+”=”+val) );
}
~~~~
So, that part is easy: we iterate through all properties of `myDay` and print their Name
and they value.
Problem is, when we reach the indexer property, we get a `TargetParameterCountException` on line 8.
We need to detect the indexer before attempting to get its value.
To do that, we need to modify our `foreach` loop to:
~~~~brush:csharp
foreach (PropertyInfo pi in picoll) {
object val;
ParameterInfo[] parcoll = pi.GetIndexParameters();
if (parcoll.Length == 0) {
val = pi.GetValue(o, null);
Debug.WriteLine(pi.Name+”=”+val) );
}
}
~~~~
Here we attempt to get the indexer parameters for every property. If we’re dealing with an indexer, the `parcoll` array will contain a list of `ParameterInfo` related to that particular indexer.
So what really is this `parcoll` and its `ParameterInfo`s?
A `ParameterInfo` contain information about the parameter of an indexer property.
The `parcoll` array will contain one entry for each overloaded indexer.
In our concrete example this is what we get:
* `pi.Name` will be _Items_, this is standard for all indexer properties.
* `pi.PropertyType` is the type of the returned class of our indexer, in our case, a _System.String_.
* `parcoll.Length` will be equal to 2 because we have 2 overloaded indexers.
* `parcoll[0].Name` will be _date_.
* `parcoll[1].Name` will be _datestr_.
* `parcoll[0].ParameterType` will be _System.DateTime_.
* `parcoll[1].ParameterType` _System.String_.
* `parcoll[0].Member` and `parcoll[1].Member` are in fact references to `pi` itself
That’s a lot of nice information about our indexer, but what if we want to get a value from it?
Consider the definition for `GetValue`:
~~~~brush:csharp
PropertyInfo.GetValue(object o, object[]{} index).
~~~~
That second parameter is used to get a value from the indexer.
###So, how do we use it?###
If you know valid keys that you can feed to the indexer, then you just do that (assuming that `pi` is the `PropertyInfo` for the indexer).
~~~~brush:csharp
string meeting;
meeting = pi.GetValue(myDay, new string[]{“2006/02/25”}) as string;
~~~~
Or, if you prefer to use a `DateTime`:
~~~~brush:csharp
string meeting;
meeting = pi.GetValue(myDay, new DateTime[]{DateTime.Now}) as string;
~~~~
But what if you don’t know anything about the values contained by the indexer?
Well, unless you use attributes to decorate the indexer or the underlying storage field with specific custom information, you’re basically screwed.
To understand why, just consider how the indexer is implemented:
When you pass it a `DateTime` or a `string` as a parameter, it just passes it on to the getter/setter as the `value`.
What is done with that is up to the implementer.
If you want to maintain a collection of some kind, then you need to use private fields to hold the keys and their values.
.Net doesn’t store the values passed to the indexer or the values returned by it anywhere.
Consider how .Net doesn’t give you any means of iterating through the keys and values of an indexer.
You can’t do a `for(;;)` or a `foreach()` loop, you can’t even know how many items are maintained through the indexer by directly questioning it.
So, it certainly is normal that you cannot get anything out of the indexer without knowing exactly how to access it.
###The Observer Effect###
In many fields of science, the act of measuring something, whether it’s an electronic circuit or a psychological experiment, can actually influence the observed phenomenon.
This is unsurprisingly called the Observer Effect.
In .Net, Reflecting on object can be very much the same.
Consider the use of Reflection when it comes to getting information about an object:
You use metadata describing the object to get more meta data information about its members
You can use that meta data to direcly query the state of an object.
If you limit yourself to getting metada, then no harm is done: you’re not querying the object instance itself but the metadata about its class.
If you start querying an actual object instance for its values then you are actively interacting with it.
In most cases, if you’re just getting the value of Fields or Properties defined as follow, then it’s fine:
~~~~brush:csharp
class GetMe {
private string _meeting;
public string Meeting {
get { return _meeting; }
set {
_meeting = value;
}
}
}
~~~~
Using reflection, we can get the values of both `_meeting` and `Meeting` without any side-effect to the object.
Now consider this:
~~~~brush:csharp
class GetMeNot {
private int _collection = null;
public int Collection {
get {
if ( _collection == null ) {
OpenDatabaseConnection();
_collection = FetchAllData();
}
return _collection;
}
}
}
~~~~
Now what we’ve got here is a case of lazy loading or lazy initialization.
The consequence is that by querying the Collection property we’re actually creating it, with a potential for a lot of side-effects, like the change of other properties, here maybe related to opening the database connection for instance.
So, when getting values of the properties of an object instance, we can actually completely change its state.
Whether this is a concern or not really depends on what you’re trying to achieve: you may not be able to achieve being a passive observer using Reflection unless you know about the construction of the particular class you’re dealing with.
Using Attributes, you could give hints about which Property not to query and which Field to get instead. In our case, we could add a custom attribute like `[ImplementedBy(“_collection”)]` to our `Collection` property for instance.
If you want to do something generic, then you’re out of luck it and have to deal with that fact.
One possible way to minimize the effect would be to not query Properties that only have a getter and no setter, but that’s not a guarantee that all properties that implement a getter/setter are going to play nice.
Then there is also the -probably rarer- case of the Property that only has a setter.
Then there is also the case of the property getter that has some in-build security and will return different things depending on who’s calling…
All this seems pretty obvious once you understand the whole picture but it’s easy to get so impressed and dazzled by the power of Reflection that you don’t realise its natural limits.
Comments
This was a nice article. I was having an issue trying to use reflection with c#, I am a Java developer, so the concept was there, just not the details. After reading severl articles, yours was the one that made the most sense. Thanks
Thanks -I’ve been trying to find the value of my index all day and nothing mentioned that it cannot be done – fortunately I found your VERY good explanation thanks (its a: “you can’t get to there from here”!) Cheers Hugh
Thanks for figuring this out. I’ve been pulling my hair out.
Very nice article. Thanks for sharing it.
HI, Here in my project a class is there which has public properties and in that properties there is one property which returns a collecton and in that collection again there are fields . Here what Iam facing is Iam getting all the properties except the inner fields of that Collection property ….Can you help me out? Thanks Andy.
Hi Andy, you should be able to reflect on the collection items just as you do for any type. When I have some time I’ll try to post a new article on reflection. Can’t promise when though.. busy at the moment. Thanks.
Nice article man, it’s exactly what I needed to know… had been trying MSDN docs for a while with no luck. Once again, a blogger saves the day! 😉
Thanks for that Renaud – a smart article. I was trying to suss out how to detect whether a property uses an indexer using reflection, and your article covered it nicely. And was an interesting read as well. Good job, sir!
@Phil: thanks. I’m glad you found it useful.
This was a very nice article, and as you articles sugested; I am screwed 🙂 Keep up the good work!
Very interesting things was highlighting by you! Thank you, really big good thank!
Very nice article and you rendered it meaningfully. I liked your emphasis on Observer Effect and once can potentially alter the state of an object (possibly without knowing). I was able to get the value of integer indexer. You are correct- there is no way to know the length or count. You have to try/catch to get the value just to get the number of iterations and go for the loop there after. Now I can see how the Observer Effect can play a role there too. My suggestion to implementers, don’t use non integer indexed property if you are going to use reflection to get the values.
Comments are closed.