1

The goal of this code is to iterate through multiple nested classes, and multiple any integer by 2. Provided simple example, however, example will be more complicated in future.

How do I change a Object to its underlying class? When I iterate through this function, it reads the type for OuterProduct correctly, but fails for InnerProduct reading as type System.RuntimeType, giving an error below

How can I resolve this code to multiply all nested integers by 2?

An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.

class Program
{
    static void Main(string[] args)
    {
        var test = new OuterProduct();
        test.AmountSold = 5;
        test.ProductName = "BookOuter";

        test.InnerProduct = new InnerProduct();
        test.InnerProduct.ProductNameInner = "BookInner";
        test.InnerProduct.AmountSoldInner = 7;

        ReadPropertiesTest.ReadPropertiesRecursive(test);
    }
}

public class OuterProduct
{
    public string ProductName { get; set; }
    public int AmountSold { get; set; }
    public InnerProduct InnerProduct { get; set; }
}

public class InnerProduct
{
    public string ProductNameInner { get; set; }
    public int AmountSoldInner { get; set; }
}

public static class ReadPropertiesTest
{
    public static void ReadPropertiesRecursive(object test)
    {
        var type = test.GetType();

        foreach (PropertyInfo property in type.GetProperties())
        {
            if (property.PropertyType == typeof(int) || property.PropertyType == typeof(int?))
            {
                property.SetValue(test, (int)(property.GetValue(test)) * 2);
            }
            if (property.PropertyType.IsClass && !(property.PropertyType == typeof(string)))
            {
                ReadPropertiesRecursive(property.PropertyType);
            }
        }
    }
}

Resources:

C#: How to get all public (both get and set) string properties of a type

How to iterate through nested properties of an object

2
  • If your int? Property is null, the cast to int will throw an exception. You should handle this case seperately. Also you will want a general null check against test in case your nested objects do not have values (assuming you make the change in the answer below) Commented Jun 11, 2020 at 20:46
  • hi @pinkfloydx33 thanks for the input, feel free to write in answer, and I can send points,! Commented Jun 11, 2020 at 20:47

3 Answers 3

2

System.RuntimeType is the implementation of the class that represents typeof(X) or something.GetType(). When you pass PropertyType to your function you are not passing the property value, but it's type.

You will need to pass the next object in the hierarchy into the recursive function by using GetValue.

Note though that this is dangerous and error prone. For example, if you have a List<> property you obviously cannot increase its Count (it is readonly!). You should check to make sure that the property can be written to using the CanWrite property.

You also need to check for null objects. On top of that we need to handle int differently from int? (otherwise casting null to int will throw). The latter we can clean up a bit with c#7 pattern matching:

public static void ReadPropertiesRecursive(object test)
{
    if (test is null) // base case 
       return;

    var type = test.GetType();

    foreach (PropertyInfo property in type.GetProperties())
    {
        // check if we can even read the property
        if(!property.CanRead)
           continue;

        // use pattern matching on the value 
        // nulls will be ignored
        // we *could* cache GetValue but then it means we will invoke it for uninteresting types/properties 
        // it's also why I don't call GetValue until we've inspected PropertyType 
        if (property.CanWrite && 
           (property.PropertyType == typeof(int) || property.PropertyType == typeof(int?)) && 
            property.GetValue(test) is int i)
        {
            property.SetValue(test, i * 2);
        }
        else if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
        {
            ReadPropertiesRecursive(property.GetValue(test));
        }
    }
}

An alternative version that omits some of the checks against PropertyType can also be used. It's a bit cleaner looking but it could potentially perform the GetValue reflection in cases where we don't need/want it (like on a double or a struct):

public static void ReadPropertiesRecursive(object test)
{
    if (test is null) // base case 
       return;

    var type = test.GetType();

    foreach (PropertyInfo property in type.GetProperties())
    {
        // check if we can even read the property
        if(!property.CanRead)
           continue;
        // possibly unnecessary if not int or class
        var val = property.GetValue(test);
        if (property.CanWrite && val is int i)
        {
            property.SetValue(test, i * 2);
        }
        else if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
        {
            ReadPropertiesRecursive(val);
        }
    }
}

Note that you may want to have a whitelist or blacklist of types. Recursing into a Type object for example isn't going to get you much.

Sign up to request clarification or add additional context in comments.

10 Comments

you can also replace property.PropertyType.IsClass && property.PropertyType != typeof(string) by property.PropertyType.IsNested
He doesn't have nested types though. OP used incorrect wording to describe a simple property hierarchy
the InnerProduct is a nested type. if not nested why the OP call ReadPropertiesRecursive recursively?
Not in the OP it's not. It's at the same namespace level nesting as OuterProduct
He's trying to recurse into nested objects /an object graph not into nested types
|
1

Alternative would be to go with more object-oriented approach. Make it responsibility of every class which need to be "updated".

For every type with properties which need to be updated introduce a method to do it.

public class OuterProduct
{
    public string ProductName { get; set; }
    public int AmountSold { get; set; }
    public InnerProduct InnerProduct { get; set; }

    public void Update()
    {
        AmountSold *= 2;
        InnerProduct.Update();
    }
}

public class InnerProduct
{
    public string ProductNameInner { get; set; }
    public int AmountSoldInner { get; set; }

    public void Update()
    {
        AmountSoldInner *= 2;
    }
}    

// Usage is simple
var test = new OuterProduct
{
    AmountSold = 5,
    ProductName = "BookOuter",
    InnerProduct = new InnerProduct
    {
        ProductNameInner = "BookInner",
        AmountSoldInner = 7
    }
};

test.Update();
// test.AmountSold == 10 is true
// test.InnerProduct.AmountSoldInner == 14 is true

This approach will simplify code maintenance. For example adding/removing properties or worse case scenario adding some other logic to Update method will be isolated in one class.

Comments

0

In your recursive call you are passing the type, not the actual property value:

if (property.PropertyType.IsClass && !(property.PropertyType == typeof(string)))
{
    ReadPropertiesRecursive(property.PropertyType);
}

should be:

if (property.PropertyType.IsClass && !(property.PropertyType == typeof(string)))
{
    ReadPropertiesRecursive(property.GetValue(test));
}

1 Comment

Yes, this. However you will likely want to add tests for null to ensure the nested objects have values.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.