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

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • 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) – pinkfloydx33 Jun 11 '20 at 20:46
  • hi @pinkfloydx33 thanks for the input, feel free to write in answer, and I can send points,! –  Jun 11 '20 at 20:47

3 Answers3

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.

pinkfloydx33
  • 11,863
  • 3
  • 46
  • 63
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.

Fabio
  • 31,528
  • 4
  • 33
  • 72
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));
}
Xerillio
  • 4,855
  • 1
  • 17
  • 28