views:

1554

answers:

5

In .NET & C#, suppose ClassB has a field that is of type ClassA. One can easily use method GetFields to list ClassB's fields. However, I want to also list the fields of those ClassB fields that themselves have fields. For example, ClassB's field x has fields b, s, and i. I'd like to (programmatically) list those fields (as suggested by my comments in the code below).

class ClassA
    {
    public byte b ;
    public short s ;
    public int i ;
    }

class ClassB
    {
    public long l ;
    public ClassA x ;
    }

class MainClass
    {
    public static void Main ( )
     {
     ClassA myAObject = new ClassA () ;
     ClassB myBObject = new ClassB () ;

     //  My goal is this:
     //    ***Using myBObject only***, print its fields, and the fields
     //    of those fields that, *themselves*, have fields.
     //  The output should look like this:
     //    Int64   l
     //    ClassA  x
     //               Byte   b
     //               Int16  s
     //               Int32  i

     }
    }
+3  A: 

Use the FieldInfo.FieldType to reflect over the type of the fields in your class. E.g.

fieldInfo.FieldType.GetFields();

Here is a complete sample based on your code that uses recursion in case you have ClassZ inside ClassA. It breaks if you have a cyclic object graph.

using System;
using System.Reflection;

class ClassA {
  public byte b;
  public short s; 
  public int i;
}

class ClassB {
  public long l;
  public ClassA x;
}

class MainClass {

  public static void Main() {
    ClassB myBObject = new ClassB();
    WriteFields(myBObject.GetType(), 0);
  }

  static void WriteFields(Type type, Int32 indent) {
    foreach (FieldInfo fieldInfo in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) {
      Console.WriteLine("{0}{1}\t{2}", new String('\t', indent), fieldInfo.FieldType.Name, fieldInfo.Name);
      if (fieldInfo.FieldType.IsClass)
        WriteFields(fieldInfo.FieldType, indent + 1);
    }
  }

}
Martin Liversage
Wow! You folks have been super-helpful! This is the first suggestion I got to work, but the other suggestions also are very good! Thanks to everyone!
JaysonFix
This definitely works for your case. If you need to continue this in the future with more complex objects or if something doesn't quite work right, please consider my answer. It was written by the C# team and the code is available for you to take and tweak as you see fit, but is flexible as-is. And you're welcome...glad these all help! :-)
Tony Heupel
+1 for cool use of indent :D
Stan R.
"if (fieldInfo.FieldType.IsClass)" is not really what you want. This would include "string" (which you don't want) and it would exclude user-defined structs (which you do want). Maybe you could just say "if (!fieldInfo.FieldType.Namespace.Equals("System"))".
Timwi
+1  A: 

The class that does this already exists! Take a look at the Microsoft C# Samples for Visual Studio: http://code.msdn.microsoft.com/Release/ProjectReleases.aspx?ProjectName=csharpsamples&ReleaseId=8

Specifically, look at the ObjectDumper sample as it goes n-levels deep. For example:

ClassB myBObject = new ClassB();
...
ObjectDumper.Write(myBObject, Int32.MaxValue); 
//Default 3rd argument value is Console.Out, but you can use 
//any TextWriter as the optional third argument

It has already taken into account whether an object in the graph has been visited, Value types vs. object types vs. enumerable types, etc.

Tony Heupel
A: 

You need to write a recursive method that takes an object, loops through its fields (obj.GetType().GetFields()), and prints the value of a field of primitive type, and calls itself for a class (other than String).

You'll need a parameter for the indent size for use with recursion.

EDIT: You'll also need some mechanism to prevent a stack overflow for cyclic object graphs. I recommend placing a limit on the indent parameter.

SLaks
A: 

Here's a naive implementation:

    private static void ListFields(Type type)
    {
        Console.WriteLine(type.Name);
        foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
        {
            Console.WriteLine(string.Format("{0} of type {1}", field.Name, field.FieldType.Name));
            if (field.FieldType.IsClass)
            {
                ListFields(field.FieldType);
            }

        }
    }

Some things to note:

  • Prevent a stack overflow. That is if a -> b and b-> a then this will blow up. You can resolve this by only resolving down to a certain level
  • A string is a reference type but lots of people expect it to be more like a value type. So you might not want to call ListFields if the type is string.
JoshBerke
A: 

Try the following. It lets you control how deep you descend into the type hierarchy and should only descend into non-primitive types.

public static class FieldExtensions
{
  public static IEnumerable<FieldInfo> GetFields( this Type type, int depth )
  {
    if( level == 0 )
      return Enumerable.Empty<FieldInfo>();

    FieldInfo[] fields = type.GetFields();
    return fields.Union(fields.Where( fi => !fi.IsPrimitive )
                              .SelectMany( f => f.FieldType.GetFields( depth -1 ) );
  }
}
LBushkin