We help IT Professionals succeed at work.

Csharp Reflection - How to iterate through inherited types?

stevehibbert
stevehibbert asked
on
I am new to C#, from a C++ background.  I am trying to write a generic ToString() fn for my types that will dump member variables to string, for diags.  
I have a routine that will dump out 'this' object's member variables and properties, and that works well.  Ideally, I would like to set up some recursion so that ToString() is inherited, and when it is called from a derived type, the derived type's members are dumped, then the base type members, then the base-of-the-base type's members, etc, down to System.Object.
So, how do I call the base class' ToString() ?
I have tried using the 'base' operator, but the virtual mechanism re-routes the function to 'this'.  I am thinking that I would have to get the current type, via reflection, and then get the base type from this somehow, and then set up a local reference that is cast to the base type, and call the ToString() function on that.
Thanks in advance for your help.
Comment
Watch Question

Most Valuable Expert 2011
Top Expert 2015

Commented:
I'm probably missing something, but is this what you are talking about?

using System;

namespace _27478455
{
    class Program
    {
        static void Main(string[] args)
        {
            Cook c = new Cook();

            c.ID = 1;
            c.HourlyWage = 7.00M;
            c.Shift = 3;

            Console.WriteLine(c.ToString());
            Console.ReadKey();
        }
    }

    class Employee
    {
        public int ID { get; set; }

        public override string ToString()
        {
            return string.Format("ID: {0}\n{1}", ID.ToString(), base.ToString());
        }
    }

    class HourlyEmployee : Employee
    {
        public decimal HourlyWage { get; set; }

        public override string ToString()
        {
            return string.Format("Hourly Wage: {0}\n{1}", HourlyWage.ToString(), base.ToString());
        }
    }

    class Cook : HourlyEmployee
    {
        public int Shift { get; set; }

        public override string ToString()
        {
            return string.Format("Shift: {0}\n{1}", Shift, base.ToString());
        }
    }
}

Open in new window

Most Valuable Expert 2011
Top Expert 2015

Commented:
P.S.

I didn't comment in the code because I was looking for you to run it an confirm the output is what you are referring to in your question. I am happy to explain how the code works if you can confirm it is what you are after  = )

Author

Commented:
I'll check this out soon - I do two jobs, and am on the 2nd job at the moment.  

Initially, I would guess 'No', because I am using a ToString function that uses reflection to find the members/properties of the class.  I am doing that, so that I can add/remove members from a class, and yet always keep the ToString function up to date.  I am trying to make the ToString recursive so that if any members are added anywhere in the hierarchy, they are accounted for automatically in the ToString function without having to update it.

When I get to it, I will post up my ToString function so that I can show you what I am talking about.  Thanks for your efforts so far, appreciate it.
Most Valuable Expert 2011
Top Expert 2015

Commented:
I am trying to make the ToString recursive so that if any members are added anywhere in the hierarchy, they are accounted for automatically in the ToString function without having to update it.
Then to that I would say you simply need to add your reflection code into logic similar to mine. Let each class be responsible for its own members. If you put your reflection logic in each class' ToString method, then you can simply call the base class' ToString method in each class (what I did above). This should give you the appropriate chaining to navigate up the inheritance chain. Just make sure you override ToString in each class.

Author

Commented:
I think I have tried to do that, but when I refer to base.ToString(), the call is virtually resolved to the derived class, rather than the base class.  I will post up my code soon, I have most probably made some error.  I will simplify it first.

Author

Commented:
OK, here is my snakey code.  Perhaps I am over-reaching myself, but I am trying to make one ToString() function that will be the same for all classes, and that will accommodate any member variables.  Currently, the recursive call to base.ToString() resolves to ClassB.ToString(), when I was hoping it would resolve to ClassA.ToString()...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace App
{
    class Program
    {
        static int Main(string[] args)
        {
            Console.WriteLine("Main has started");

            ClassB classb = new ClassB();
            Console.WriteLine("DIAGNOSTIC: {0}", classb.ToString());

            Console.WriteLine("Main is about to finish");
            Console.WriteLine();
            Console.ReadLine();
            return 0;

        } // end of Main()

    } // end of class Program

    public class ClassA
    {
        ///////////////
        // Base Class

        ////////////
        // METHODS

        public override string ToString()
        {
            StringBuilder sbReturn = new StringBuilder();
            sbReturn.Capacity = 511;
            sbReturn.Append("[");

            FieldInfo[] instancefields = (this.GetType()).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            // Loop through
            int i = 0;
            foreach (FieldInfo instancefield in instancefields)
            {
                if (i++ != 0)
                    sbReturn.Append(";");

                sbReturn.Append(instancefield.Name);
                sbReturn.Append(": ");
                sbReturn.Append(instancefield.GetValue(this));
            }

            sbReturn.Append("]");

            sbReturn.Append(base.ToString());

            return sbReturn.ToString();
        }

        ////////////
        // MEMBERS
        int m_iValueA;
    }

    public class ClassB : ClassA
    {
        //////////////////
        // Derived Class

        ////////////
        // METHODS
        public override string ToString()
        {
            StringBuilder sbReturn = new StringBuilder();
            sbReturn.Capacity = 511;
            sbReturn.Append("[");

            FieldInfo[] instancefields = (this.GetType()).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            // Loop through
            int i = 0;
            foreach (FieldInfo instancefield in instancefields)
            {
                if (i++ != 0)
                    sbReturn.Append(";");

                sbReturn.Append(instancefield.Name);
                sbReturn.Append(": ");
                sbReturn.Append(instancefield.GetValue(this));
            }

            sbReturn.Append("]");

            sbReturn.Append(base.ToString());

            return sbReturn.ToString();
        }


        ////////////
        // MEMBERS
        string m_sValueB;
    }

} // end of namespace App

Open in new window

Most Valuable Expert 2011
Top Expert 2015

Commented:
The "problem" is in line 41. Even though you are using this what type is classb in Main? It is of type ClassB, and just because you are working in ClassA in line 41, you haven't changed the type of the object. This means your call to GetType in line 41 still returns the type, and subsequently the members, of ClassB. I don't know the resolution off-hand, but I'll take a look at it. Someone else might pop in with a correction as well.

Stay tuned...    = )
Most Valuable Expert 2011
Top Expert 2015

Commented:
Try this. There is an alternate way of getting the type of a class:

typeof(ClassName)

Instead of calling the GetFields member on the instance (i.e. this), pass the type determined on the class name itself:

public class ClassA
{
    public override string ToString()
    {
        ...
        FieldInfo[] instancefields = (typeof(ClassA)).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
        ...
    }
}

Open in new window


...and

public class ClassB
{
    public override string ToString()
    {
        ...
        FieldInfo[] instancefields = (typeof(ClassB)).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
        ...
    }
}

Open in new window

Most Valuable Expert 2011
Top Expert 2015

Commented:
Ignore the BindingFlags.FlattenHierarchy. I was testing some things out, but I forgot to remove it before posting. Removing it will have no effect on the results of the code above.

Author

Commented:
Ah, yes, but then the code in ClassA.ToString() is different from the code in ClassB.ToString().  I was aiming (admittedly, ambitiously) for a recursive function that would work out it's type within the code, without it being hard-coded.  I am aiming to do a relative cast to the current class's base class, and recurse down the class hierarchy to the System.Object.  The real nitty-gritty of the question is how do I call the base class's ToString() so that it resolves correctly?  I am thinking that I need to work out the current Type (say, ClassB) and then the relative base Type (ie ClassA) and then call the ToString() function of the base type - but how do I do that?  I can find the base Type by using GetType(), and then finding the BaseType property, but having obtained the BaseType as a string, how do I then create a reference of this type?  It seems that once I have created a reference of the base type, from the current type, I should be able to call the base type's ToString(), and I have recursion.
It's not crucial that this works - it's far more important that I understand how to do it, and if it's not possible, why it's not possible.
Most Valuable Expert 2011
Top Expert 2015
Commented:
But in each ToString method you know the class you are working in, so why would it be an issue? Whether you have to type "this.GetType()" or "typeof(ClassA)", you still typed the same number of characters. The only thing you have potentially gained is that the runtime would determine the type when using "this.GetType()".

I can find the base Type by using GetType(), and then finding the BaseType property, but having obtained the BaseType as a string, how do I then create a reference of this type?
Well BaseType returns its value as type Type, which is also what GetFields expects, so why not pass that? If the issue is having one ToString method that can loop over the whole hierarhcy (from within the child class alone), then you should be able to use a loop:

public class ClassB : ClassA
{
    //////////////////
    // Derived Class

    ////////////
    // METHODS
    public override string ToString()
    {
        Type myType = this.GetType();
        StringBuilder sbReturn = new StringBuilder();
        sbReturn.Capacity = 511;
        sbReturn.Append("[");

        while (myType != typeof(object))
        {
            FieldInfo[] instancefields = myType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            // Loop through
            int i = 0;
            foreach (FieldInfo instancefield in instancefields)
            {
                if (i++ != 0)
                    sbReturn.Append(";");

                sbReturn.Append(instancefield.Name);
                sbReturn.Append(": ");
                sbReturn.Append(instancefield.GetValue(this));
            }

            myType = myType.BaseType;
        }

        sbReturn.Append("]");
        
        return sbReturn.ToString();
    }


    ////////////
    // MEMBERS
    string m_sValueB;
}

Open in new window


Take note of the while loop that proceeds up the type hierarchy.

Author

Commented:
Yes, that's great, it does exactly what I need!  I can now include this code in any class, and it will give me a ToString() that dumps all members, properties and autoproperties, even as I expand the classes at any level of the hierarchy.  For example, if I add a string to ClassA, the string is 'visible' to ClassB, and any classes derived from ClassB.
I have moved the counter variable i out of the loop so that the semicolon logic is right, and added a ClassC, based on ClassB, and the demo code is attached below.  Thanks kaufmed, very happy with this solution, points to you.

 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace App
{
    class Program
    {
        static int Main(string[] args)
        {
            Console.WriteLine("Main has started");

            ClassB classb = new ClassB();
            Console.WriteLine("DIAGNOSTIC: ClassB: {0}", classb.ToString());

            ClassC classc = new ClassC();
            Console.WriteLine("DIAGNOSTIC: ClassC: {0}", classc.ToString());

            Console.WriteLine("Main is about to finish");
            Console.WriteLine();
            Console.ReadLine();
            return 0;

        } // end of Main()

    } // end of class Program

    public class ClassA
    {
        ///////////////
        // Base Class

        ////////////
        // METHODS

        public override string ToString()
        {
            Type myType = this.GetType();
            StringBuilder sbReturn = new StringBuilder();
            sbReturn.Capacity = 511;
            sbReturn.Append("[");

            int i = 0;
            while (myType != typeof(object))
            {
                FieldInfo[] instancefields = myType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

                // Loop through
                foreach (FieldInfo instancefield in instancefields)
                {
                    if (i++ != 0)
                        sbReturn.Append(";");

                    sbReturn.Append(instancefield.Name);
                    sbReturn.Append(": ");
                    sbReturn.Append(instancefield.GetValue(this));
                }

                myType = myType.BaseType;
            }

            sbReturn.Append("]");

            return sbReturn.ToString();
        }


        ////////////
        // MEMBERS
        int m_iValueA;
        string m_sValueA;
    }

    public class ClassB : ClassA
    {
        //////////////////
        // Derived Class

        ////////////
        // METHODS
        
        public override string ToString()
        {
            Type myType = this.GetType();
            StringBuilder sbReturn = new StringBuilder();
            sbReturn.Capacity = 511;
            sbReturn.Append("[");

            int i = 0;
            while (myType != typeof(object))
            {
                FieldInfo[] instancefields = myType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

                // Loop through
                foreach (FieldInfo instancefield in instancefields)
                {
                    if (i++ != 0)
                        sbReturn.Append(";");

                    sbReturn.Append(instancefield.Name);
                    sbReturn.Append(": ");
                    sbReturn.Append(instancefield.GetValue(this));
                }

                myType = myType.BaseType;
            }

            sbReturn.Append("]");

            return sbReturn.ToString();
        }


        ////////////
        // MEMBERS
        string m_sValueB;
    }


    public class ClassC : ClassB
    {
        /////////////////////////////////////
        // Derived Class from Derived Class


        ////////////
        // METHODS

        public override string ToString()
        {
            Type myType = this.GetType();
            StringBuilder sbReturn = new StringBuilder();
            sbReturn.Capacity = 511;
            sbReturn.Append("[");

            int i = 0;
            while (myType != typeof(object))
            {
                FieldInfo[] instancefields = myType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

                // Loop through
                foreach (FieldInfo instancefield in instancefields)
                {
                    if (i++ != 0)
                        sbReturn.Append(";");

                    sbReturn.Append(instancefield.Name);
                    sbReturn.Append(": ");
                    sbReturn.Append(instancefield.GetValue(this));
                }

                myType = myType.BaseType;
            }

            sbReturn.Append("]");

            return sbReturn.ToString();
        }


        ////////////
        // MEMBERS
        double m_dValueC;
    }

} // end of namespace App

Open in new window



Author

Commented:
For reference, I needed this for diagnostics, and so that the Equals() and GetHashCode() overrides would work transparently also - they just call ToString() in effect.