简体   繁体   中英

Loop on GetFields and GetProperties of a Type?

Here is my code :

var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
    //some code
}

var props = type.GetProperties();
foreach (PropertyInfo prop in props)
{
    //exact same code
}

I know I can create a function that I could call twice but what I'd like to do (if possible) is a single foreach . Something like this (yes, the code doesn't work. If it worked, I wouldn't ask the question !):

var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
var props = type.GetProperties();

foreach (PropertyInfo prop in fields && PropertyInfo prop in props)
{
    //some code
}

I really feel like there is a way, even if I know that my solution is far from being compilable :(
Thanks for any help !

If you're OK with properties exposed by MemberInfo class (which is base for both FieldInfo and PropertyInfo) then you may do the following:

var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance).Cast<MemberInfo>();
var props = type.GetProperties().Cast<MemberInfo>();
var fieldsAndProperties = fields.Union(props);

foreach (MemberInfo mi in fieldsAndProperties)
{
...
}

If the functionality exposed by MemberInfo is sufficient for you in the loop then you can concatenate both fields and properties on the same array or other enumerable and iterate only once.

This is possible because both FieldInfo and PropertyInfo inherit from the MemberInfo class.

Sample code:

var fields = typeof(DateTime).GetFields(
    BindingFlags.Public | BindingFlags.Instance);

var properties = typeof(DateTime).GetProperties(
    BindingFlags.Public | BindingFlags.Instance);

var all = fields.Cast<MemberInfo>().Concat(properties.Cast<MemberInfo>());

foreach (MemberInfo mi in all)
{
    //some code
}

To put them into one loop:

foreach (MemberInfo member in fields.Cast<MemberInfo>().Concat(props))
{ 
    //I'm 100% sure this isn't what you want
    //because you need to set value here
    //in this case, you still need to check if member is a FieldInfo/PropertyInfo 
    //and then convert it before you set the values
}

Actually, using two loops is probably better here. Firstly the code looks clearer, because they are doing different things, one sets values of fields and another is for properties. Secondly, register caches probably helps to make your program a little faster than one loop.

Hmmm....

This kind of iteration is loosing order of fields and properties. Eg

class Test
{
    public String tests { get; set; }
    public int    testi;
}

If GetFields if get first - we get "testi" as first memberm and via GetProperies we get "tests". Union probably will merge them but order is not preserved.

Is there any way to get fields and properties and preserve order of them ?

I suggest following to traverse an object and log object information.

public static string ObjectToString(object obj)
{
    var sb = new StringBuilder();
    try
    {
        sb.AppendLine(obj.GetType().Name);
        foreach (var prop in obj.GetType().GetProperties())
        {
            sb.AppendLine(string.Format("{0} Property Name: {1}; Value: [{2}]; Type: {3}", "--->", prop.Name, prop.GetValue(obj, null)?.ToString(), prop.PropertyType?.FullName));
        }
        foreach (var fld in obj.GetType().GetFields())
        {
            if (!fld.FieldType.Namespace.Equals("System", StringComparison.InvariantCultureIgnoreCase) && fld.GetValue(obj) != null)
            {
                ObjectToString(fld.GetValue(obj), sb);
            }
            else
            {
                sb.AppendLine(string.Format("{0} Field Name: {1}; Value: [{2}]; Type: {3}", "--->", fld.Name, fld.GetValue(obj)?.ToString(), fld.FieldType?.FullName));
            }
        }
    }
    catch (Exception ex)
    {
        sb.AppendLine("---> Exception in ObjectToString: " + ex.Message);
    }
    return sb.ToString();
}
public static string ObjectToString(object obj, StringBuilder sb, int depth = 1)
{
    try
    {
        sb.AppendLine(string.Format("{0}{1} {2}", (depth==2?"----":""),"--->", obj.GetType().Name));
        foreach (var prop in obj.GetType().GetProperties())
        {
            sb.AppendLine(string.Format("{0} Property Name: {1}; Value: [{2}]; Type: {3}", "------->", prop.Name, prop.GetValue(obj, null)?.ToString(), prop.PropertyType?.FullName));
        }
        foreach (var fld in obj.GetType().GetFields())
        {
            //we want to avoid stake overflow and go only one more depth
            if (!fld.FieldType.Namespace.Equals("System", StringComparison.InvariantCultureIgnoreCase) && fld.GetValue(obj) != null && depth < 2)
            {
                ObjectToString(fld.GetValue(obj), sb, 2);
            }
            else
            {
                sb.AppendLine(string.Format("{0} Field Name: {1}; Value: [{2}]; Type: {3}", "------->", fld.Name, fld.GetValue(obj)?.ToString(), fld.FieldType?.FullName));
            }
        }

    }
    catch (Exception ex)
    {
        sb.AppendLine("-------> Exception in ObjectToString: depth(" + depth + ") " + ex.Message);
    }
    return sb.ToString();
}

One issue with accessing member properties with reflection is so that it is slow. There is small library which does that job really well and fast with some dynamically generated accessors code, FastMember . To iterate all public fields and properties with it you need to run simple loop like this:

var ta = TypeAccessor.Create(typeof(MyClass));
foreach (var prop in ta.GetMembers())
{
    Console.WriteLine(prop.Name);

}

When you need to set value of field or property of any object instance that can be done like this:

var instance = new MyClass();
ta[instance, prop.Name] = "myvalue";

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM