tags:

views:

612

answers:

5

Given the following objects:

public class Customer {
    public String Name { get; set; }
    public String Address { get; set; }
}

public class Invoice {
    public String ID { get; set; }
    public DateTime Date { get; set; }
    public Customer BillTo { get; set; }
}

I'd like to use reflection to go through the Invoice to get the Name property of a Customer. Here's what I'm after, assuming this code would work:

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address");
Object val = info.GetValue(inv, null);

Of course, this fails since "BillTo.Address" is not a valid property of the Invoice class.

So, I tried writing a method to split the string into pieces on the period, and walk the objects looking for the final value I was interested in. It works okay, but I'm not entirely comfortable with it:

public Object GetPropValue(String name, Object obj) {
    foreach (String part in name.Split('.')) {
        if (obj == null) { return null; }

        Type type = obj.GetType();
        PropertyInfo info = type.GetProperty(part);
        if (info == null) { return null; }

        obj = info.GetValue(obj, null);
    }
    return obj;
}

Any ideas on how to improve this method, or a better way to solve this problem?

EDIT after posting, I saw a few related posts... There doesn't seem to be an answer that specifically addresses this question, however. Also, I'd still like the feedback on my implementation.

A: 

You have to access the ACTUAL object that you need to use reflection on. Here is what I mean:

Instead of this:

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address");
Object val = info.GetValue(inv, null);

Do this (edited based on comment):

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo");
Customer cust = (Customer)info.GetValue(inv, null);

PropertyInfo info2 = cust.GetType().GetProperty("Address");
Object val = info2.GetValue(cust, null);

Look at this post for more information: Using reflection to set a property of a property of an object

Gabriel McAdams
Thanks for the answer... I know how to get the value of a first-level property, but I was wondering how to get a nested one. In my actual application, I don't have access to the actual object.
jheddings
+3  A: 

I actually think your logic is fine. Personally, I would probably change it around so you pass the object as the first parameter (which is more inline with PropertyInfo.GetValue, so less surprising).

I also would probably call it something more like GetNestedPropertyValue, to make it obvious that it searches down the property stack.

Reed Copsey
Good call on reordering the parameters and the suggested name change.
itowlson
Thanks for the feedback... I've taken both suggestions into my implementation. I ended up turning it into an extension method on the `Object` class, which strengthens the point on reordering the parameters.
jheddings
+2  A: 

You don't explain the source of your "discomfort," but your code basically looks sound to me.

The only thing I'd question is the error handling. You return null if the code tries to traverse through a null reference or if the property name doesn't exist. This hides errors: it's hard to know whether it returned null because there's no BillTo customer, or because you misspelled it "BilTo.Address"... or because there is a BillTo customer, and its Address is null! I'd let the method crash and burn in these cases -- just let the exception escape (or maybe wrap it in a friendlier one).

itowlson
Good point... I've updated my code with that in mind.
jheddings
+2  A: 
   if (info == null) { /* throw exception instead*/ }

I would actually throw an exception of they request a property that doesn't exist. The way you have it coded, if I call GetPropValue and it returns null, I don't know if that means the property didn't exist, or the property did exist but it's value was null.

AaronLS
In addition, move the check for obj being null outside the loop.
Kevin Brock
Sorry, didn't see the repeated use of obj. It's not good programming practice to change your parameters, this can lead to confusion in the future. Use a different variable for the obj parameter to traverse within the loop.
Kevin Brock
Kevin: In order to use a different variable, he'd have to either assign it to obj at the end, or make the method recursive. Personally, I don't think this is a problem (although a good comment would be nice...)
Reed Copsey
A: 

Try inv.GetType().GetProperty("BillTo+Address");

ram
Hmmm... That didn't work for me.
jheddings