C# Reflection with recursion
Asked Answered
R

5

5

I am working on the Reflection , but i am stuck while doing the recursion.

Code :

public class User {
  public string Name;
  public int Number;
  public Address Address;    
}


public class Address {
 public string Street;
 public string State;
 public string Country;
}

now i am printing the values.

 Type t = user.GetType();  
 PropertyInfo[] props = t.GetProperties(); 
 foreach (PropertyInfo prp in props)  
 {  
   if(!prp.GetType().IsPrimitive && prp.GetType().IsClass) 
   {
     // Get the values of the Inner Class.
     // i am stucked over here , can anyone help me with this.

           Type ty = prp.GetType();
           var prpI = ty.GetProperties();
           //var tp = ty.GetType().;
            foreach (var propertyInfo in prpI)
            {
            var value = propertyInfo.GetValue(prp);
            var stringValue = (value != null) ? value.ToString() : "";
            console.WriteLine(prp.GetType().Name + "." + propertyInfo.Name+" Value : " +stringValue);    
            }
   }
   else
   {    
     var value = prp.GetValue(user);   
     var stringValue = (value != null) ? value.ToString() : "";
     console.writeline(user.GetType().Name + "." + prp.Name+" Value : " +stringValue); 
   }
 }

i want to know how to find out whether the property is a class or the primitive. and do the recursion if it is a class.

Rideout answered 4/2, 2013 at 9:15 Comment(7)
Why don't you post something that at least compiles?Postexilian
Are you sure that reflection is the solution of the problem? Why do you want to do this?Advisee
Why don't you try Automapper it will do the work.Twocolor
If you want to work this way, a Dictionary instead of a class would do the work.Venavenable
@IamStalker , the Automapper will work surely, but i want to write this code on my own. so anyone can help me how to find out whether the property is primitive or its a class.Rideout
That sounds unnecessary, don't fall into the "Not invented here" trap...Headpin
possible duplicate of Nested classes and recursionRooky
S
16

First of all, if you want to access the properties of a type, ensure you are using a type which has properties:

public class User {
  public string Name{get;set;}
  public int Number{get;set;}
  public Address Address{get;set;}    
}


public class Address {
 public string Street{get;set;}
 public string State{get;set;}
 public string Country{get;set;}
}

Second, prp.GetType() will always return PropertyInfo. You are looking for prp.PropertyType, which will return the Type of the property.

Also, if(!prp.GetType().IsPrimitive && prp.GetType().IsClass) won't work the way you want, because String e.g. is a class and also not a primitive. Better use prp.PropertyType.Module.ScopeName != "CommonLanguageRuntimeLibrary".

Last but not least, to use recursion, you actually have to put your code into a method.

Here's a complete example:

IEnumerable<string> GetPropertInfos(object o, string parent=null)
{
    Type t = o.GetType();  
    PropertyInfo[] props = t.GetProperties(BindingFlags.Public|BindingFlags.Instance);
    foreach (PropertyInfo prp in props)  
    {  
        if(prp.PropertyType.Module.ScopeName != "CommonLanguageRuntimeLibrary")
        {
            // fix me: you have to pass parent + "." + t.Name instead of t.Name if parent != null
            foreach(var info in GetPropertInfos(prp.GetValue(o), t.Name))
                yield return info; 
        }
        else
        {    
            var value = prp.GetValue(o);   
            var stringValue = (value != null) ? value.ToString() : "";
            var info = t.Name + "." + prp.Name + ": " + stringValue;
            if (String.IsNullOrWhiteSpace(parent))
                yield return info; 
            else
                yield return parent + "." + info; 
        }
    }
}

Used like this:

var user = new User { Name = "Foo", Number = 19, Address = new Address{ Street="MyStreet", State="MyState",  Country="SomeCountry" }    };
foreach(var info in GetPropertInfos(user))
    Console.WriteLine(info);

it will output

User.Name: Foo
User.Number: 19
User.Address.Street: MyStreet
User.Address.State: MyState
User.Address.Country: SomeCountry
Stove answered 4/2, 2013 at 9:45 Comment(7)
thanks a ton bro, the output is exactly as i needed, just outstanding , i have been scratching my head since morning , just wanted to say a BIG THANKS.Rideout
@sloth: This has the potential to cause stackoverflow in the case of a recursive class. ie The User class has a property that is also a User. In general all recursive methods should have some way to prevent an infinite recursion case. (Like using a maxDepth argument)Twink
@Twink True, but I think a more problematic scenario to watch out for are circular dependencies, like e.g. a User istance referencing an Address instance which in turn references the same User instance again.Stove
@sloth: Indeed that is what I meant by a recursive class. A User class that also contains a User property (or any Property that has a User property). I agree circular reference is the better term, as infinite recursion would be the result.Twink
if the User class is in a different class library I don't see how this code would work. The Module.ScopeName would then be something like WhateverClassLibrary.dllDvinsk
Since "WhateverClassLibrary.dll" != "CommonLanguageRuntimeLibrary" I don't see your problem.Stove
My problem is that at the second level of the recursion my Type t is equal to {Name = "RuntimeType" FullName = "System.RuntimeType"}. What can i do?Sympathin
A
3

First of all, avoid using reflection unless you really need it. It's slow, it's messy, it's borderline undebuggable (and I love it, but that's another thing)

If you want to dump the whole content of your object, I would recommend to transfer that to the objects themselves, they should know their inner state. You could use the objects ToString method to write their inner state, or you could define an interface for displaying the inner state

interface IStateDisplay
{
   string GetInnerState();
}

and make your objects implement it. Then the code for displaying the properties will be

Console.WriteLine(user.GetInnerState());

Another option would be to use a tool like AutoMapper that hides the complexities and intricacies of reflection from you, and exposes a nice API to use.

However, if you are learning about reflection, printing the state of a complex object is a nice exercise. A few pointers in that direction:

public string Name;

is not a property, it's a field, so type.GetProperties() will not return it. Read up on what C# properties are, and how they are used and defined. A minimal property declaration is

public string Name {get; set;}

Also, prp.GetType() will return the type information for the PropertyInfo type, not for the type of the property it contains. What you need in this case is the prp.PropertyType property.

Next, I don't think the Type.IsPrimitive check is what you want it to be. That property returns true for the Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single types, and false for everything else. Most importantly typeof(string).IsPrimitive returns false.

On the same note, I don't think the Type.IsClass check is what you want it to be, either. As you are using it, that only check if the property is of a value or of a reference type, and as value types (struct) can be also very complex and contain properties and fields of their own, the check does not make sense.

Advisee answered 4/2, 2013 at 10:4 Comment(1)
Concept of implementing a state interface is pure gold. WAY better than a bunch of complicated and unneeded reflection. I used a state interface on some of my view models that had (common) ID properties that I needed to access in an action filter. Works awesome. Thx.Bareheaded
R
0
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;


namespace Extensions
{
    public static class ObjectExtension
    {
        public static string ToStringProperties(this object o)
        {
            return o.ToStringProperties(0);
        }
    public static string ToStringProperties(this object o, int level)
    {
        StringBuilder sb = new StringBuilder();
        string spacer = new String(' ', 2 * level);
        if (level == 0) sb.Append(o.ToString());
        sb.Append(spacer);
        sb.Append("{\r\n");
        foreach (PropertyInfo pi in o.GetType().GetProperties())
        {
        if (pi.GetIndexParameters().Length == 0)
        {
            sb.Append(spacer);
            sb.Append("  ");
            sb.Append(pi.Name);
            sb.Append(" = ");

            object propValue = pi.GetValue(o, null);
            if (propValue == null)
            {
                sb.Append(" <null>");
            } else {
                if (IsMyOwnType(pi.PropertyType))
                {
                    sb.Append("\r\n");
                    sb.Append(((object)propValue).ToStringProperties(level + 1));
                } else{
                    sb.Append(propValue.ToString());
                }
            }
            sb.Append("\r\n");
        }
    }
    sb.Append(spacer);
    sb.Append("}\r\n");
    return sb.ToString();
}
    private static bool IsMyOwnType(Type t) 
{
    return (t.Assembly == Assembly.GetExecutingAssembly());
}
}
}
Ryanryann answered 4/2, 2013 at 9:39 Comment(0)
K
0

Thanks @Sloth, you code is very useful. This is a slight modification those who are getting an "Object reference not set to an instance of an object." error. It creates an instance of the object is null and also handles arrays. More has to be done to handle all possible collection types but this is a start.

public static IEnumerable<string> GetPropertInfos(object o, string parent = null)
    {


        Type t = o.GetType();
        //   String namespaceValue = t.Namespace;


        PropertyInfo[] props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (PropertyInfo prp in props)
        {
            if (prp.PropertyType.Module.ScopeName != "CommonLanguageRuntimeLibrary")
            {
                // fix me: you have to pass parent + "." + t.Name instead of t.Name if parent != null
                object value = prp.GetValue(o);
                if (value == null)
                {

                    value =
                        Activator.CreateInstance(Type.GetType(
                            (prp.PropertyType).AssemblyQualifiedName.Replace("[]", "")));
                }

                var propertInfos = GetPropertInfos(value, t.Name);
                foreach (var info in propertInfos)
                    yield return info;
            }
            else
            {
                var type = GetTypeName(prp);

                var info = t.Name + "." + prp.Name ;
                if (String.IsNullOrWhiteSpace(parent))
                    yield return info;
                else
                    yield return parent + "." + info;
            }
        }
    }
Kuster answered 26/10, 2015 at 13:37 Comment(0)
E
-1

You may use the Type.IsValueType property.

Effeminize answered 4/2, 2013 at 9:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.