可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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
}
}
回答1:
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);
}
}
}
回答2:
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.
回答3:
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( depth == 0 )
return Enumerable.Empty<FieldInfo>();
FieldInfo[] fields = type.GetFields();
return fields.Union(fields.Where( fi => !fi.IsPrimitive )
.SelectMany( f => f.FieldType.GetFields( depth -1 ) );
}
}
回答4:
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.
回答5:
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.