8

I'm writing my own method to convert an object graph to a custom object since the JavaScriptSerializer fires errors on null values.

So this is what I have so far:

internal static T ParseObjectGraph<T>(Dictionary<string, object> oGraph)
{
    T generic = (T)Activator.CreateInstance<T>();
    Type resType = typeof(T);
    foreach (PropertyInfo pi in resType.GetProperties())
    {
        object outObj = new object();
        if (oGraph.TryGetValue(pi.Name.ToLower(), out outObj))
        {
            Type outType = outObj.GetType();
            if (outType == pi.PropertyType)
            {                        
                pi.SetValue(generic, outObj, null);
            }
        }
    }
    return generic;
}

Now the pi.SetValue() method runs, and doesn't fire an error but when I look at the properties of generic, it's still the same as it was before hand.

The first property it goes through is a boolean so the values end up like this

generic = an object of type MyCustomType
generic.property = false
outObj = true
pi = boolean property
outType = boolean

Then after the SetValue method runs, generic.property is still set to false.

James Hay
  • 7,115
  • 6
  • 33
  • 57
  • Unrelated but `generic.GetType()` would have better performance than `typeof(T)` from my benchmarks. There's also no reason to cast the result of `Activator.CreateInstance`, it already returns an instance of `T`. – M.Babcock Mar 14 '12 at 00:51
  • Thankyou, I will change those things. Also I've found the answer by chance. – James Hay Mar 14 '12 at 00:55
  • You need to debug this. if (outType == pi.PropertyType) makes no sense, can't tell what you're trying to do. – Hans Passant Mar 14 '12 at 01:03
  • @HansPassant checking to see if they are the same type. – James Hay Mar 14 '12 at 01:13

3 Answers3

15

PropertyInfo.SetValue/GetValue worked with struct with accurate using

struct Z
{
  public int X { get; set; }
}

  Z z1 = new Z();
  z1.GetType().GetProperty("X").SetValue(z1, 100, null);
  Console.WriteLine(z1.X); //z1.X dont changed

  object z2 = new Z();
  z2.GetType().GetProperty("X").SetValue(z2, 100, null);
  Console.WriteLine(((Z)z2).X); //z2.x changed to 100

  Z z3 = new Z();
  object _z3 = z3;
  _z3.GetType().GetProperty("X").SetValue(_z3, 100, null);
  z3 = (Z)_z3;
  Console.WriteLine(z3.X); //z3.x changed to 100

Correct way to change struct:

  • box struct
  • change property of boxed struct
  • assign boxed struct to source
Serj-Tm
  • 16,581
  • 4
  • 54
  • 61
7

Found the answer. Apparently, PropertyInfo.SetValue() and PropertyInfo.GetValue() do not work for structures, only classes.

MyCustomType was unfortunately a struct, so changing this to a class made it work.

The 3rd reply in this thread states why structures do not work and classes do.

EDIT: It does work with structs, see the marked answer.

James Hay
  • 7,115
  • 6
  • 33
  • 57
  • 1
    Structures do work- you just need to be sure to unbox the structure and return that, rather than returning the original pre-boxed value. See my answer for how to do that. – Chris Shain Mar 14 '12 at 01:05
6

So I took your method and made a unit test of it:

class PropertySetTest
{
    static readonly Type resType = typeof(Car);
    internal static T ParseObjectGraph<T>(Dictionary<string, object> oGraph)
    {
        T generic = (T)Activator.CreateInstance<T>();
        foreach (PropertyInfo pi in resType.GetProperties())
        {
            //No need to new() this
            object outObj; // = new object();
            if (oGraph.TryGetValue(pi.Name.ToLower(), out outObj))
            {
                Type outType = outObj.GetType();
                if (outType == pi.PropertyType)
                {
                    pi.SetValue(generic, outObj, null);
                }
            }
        }
        return generic;
    }

    [Test]
    public void Test()
    {
        var typeData = new Dictionary<String, Object> {{"color", "Blue"}};
        var myCar = ParseObjectGraph<Car>(typeData);
        Assert.AreEqual("Blue", myCar.Color);
    }
}

internal class Car
{
    public String Color { get; set; }
}

This passes. Can you make it not pass in the way that you are seeing?

EDIT: With your struct, it is only marginally more complicated. See Jon Skeet's answer here regarding what's going on. As for the working code:

class PropertySetTest
{
    static readonly Type resType = typeof(Car);
    internal static T ParseObjectGraph<T>(Dictionary<string, object> oGraph)
    {
        Object generic = Activator.CreateInstance<T>();
        foreach (var pi in resType.GetProperties())
        {
            //No need to new() this
            object outObj; // = new object();
            if (oGraph.TryGetValue(pi.Name.ToLower(), out outObj))
            {
                var outType = outObj.GetType();
                if (outType == pi.PropertyType)
                    pi.SetValue(generic, outObj, null);
            }
        }
        return (T)generic;
    }

    [Test]
    public void Test()
    {
        var typeData = new Dictionary<String, Object> {{"color", "Blue"}};
        var myCar = ParseObjectGraph<Car>(typeData);
        Assert.AreEqual("Blue", myCar.Color);
    }
}

internal struct Car
{
    public String Color { get; set; }
}
Community
  • 1
  • 1
Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • 1
    Thanks, the reason this passed is that `Car` is a class. My object was a struct. – James Hay Mar 14 '12 at 00:57
  • Yup that did the trick. So what did I really change, instead of having `generic` as type `T` I just use it as an object? – James Hay Mar 14 '12 at 01:10
  • 2
    The trick is that when you use the generic method with a struct (which is a value type) the semantics change. Converting a value type (your `generic` variable) into an `Object` (called boxing it) creates a *new copy* of that value type on the managed heap. So in your original example, you box the value type (making a copy) *then set the properties on the copy*, then return the original (un-initialized) one. All I changed was where I cast it to a `T`. – Chris Shain Mar 14 '12 at 01:15