Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Type.GetFields() return backing fields in a base class?

In C#, if you use Type.GetFields() with a type representing a derived class, it will return a) all explicitly declared fields in the derived class, b) all backing fields of automatic properties in the derived class and c) all explicitly declared fields in the base class.

Why are the d) backing fields of automatic properties in the base class missing?

Example:

public class Base {
    public int Foo { get; set; }
}
public class Derived : Base {
    public int Bar { get; set; }
}
class Program {
    static void Main(string[] args) {
        FieldInfo[] fieldInfos = typeof(Derived).GetFields(
            BindingFlags.Public | BindingFlags.NonPublic |
            BindingFlags.Instance | BindingFlags.FlattenHierarchy
        );
        foreach(FieldInfo fieldInfo in fieldInfos) {
            Console.WriteLine(fieldInfo.Name);
        }
    }
}

This will show only the backing field of Bar, not Foo.

like image 793
Cygon Avatar asked Feb 08 '12 21:02

Cygon


3 Answers

A field being a backing field has no influence on reflection. The only relevant property of backing fields is that they are private.

Reflection functions don't return private members of base classes, even if you use FlattenHierarchy. You will need to loop manually over your class hierarchy and ask for private fields on each one.

I think FlattenHierarchy is written with the intent to show all members visible to code in the class you look at. So base members can be hidden/shadowed by members with the same name in a more derived class and private members are not visible at all.

like image 107
CodesInChaos Avatar answered Nov 15 '22 17:11

CodesInChaos


Here is a revised version using HashSet:

public static FieldInfo[] GetFieldInfosIncludingBaseClasses(Type type, BindingFlags bindingFlags)
{
    FieldInfo[] fieldInfos = type.GetFields(bindingFlags);

    // If this class doesn't have a base, don't waste any time
    if (type.BaseType == typeof(object))
    {
        return fieldInfos;
    }
    else
    {   // Otherwise, collect all types up to the furthest base class
        var currentType = type;
        var fieldComparer = new FieldInfoComparer();
        var fieldInfoList = new HashSet<FieldInfo>(fieldInfos, fieldComparer);
        while (currentType != typeof(object))
        {
            fieldInfos = currentType.GetFields(bindingFlags);
            fieldInfoList.UnionWith(fieldInfos);
            currentType = currentType.BaseType;
        }
        return fieldInfoList.ToArray();
    }
}

private class FieldInfoComparer : IEqualityComparer<FieldInfo>
{
    public bool Equals(FieldInfo x, FieldInfo y)
    {
        return x.DeclaringType == y.DeclaringType && x.Name == y.Name;
    }

    public int GetHashCode(FieldInfo obj)
    {
        return obj.Name.GetHashCode() ^ obj.DeclaringType.GetHashCode();
    }
}
like image 26
Piper Avatar answered Nov 15 '22 18:11

Piper


Thanks go to @CodeInChaos for the quick and complete answer!

In case anyone else stumbles across this, here's a quick workaround that follows the fields up to the furthest base class.

/// <summary>
///   Returns all the fields of a type, working around the fact that reflection
///   does not return private fields in any other part of the hierarchy than
///   the exact class GetFields() is called on.
/// </summary>
/// <param name="type">Type whose fields will be returned</param>
/// <param name="bindingFlags">Binding flags to use when querying the fields</param>
/// <returns>All of the type's fields, including its base types</returns>
public static FieldInfo[] GetFieldInfosIncludingBaseClasses(
    Type type, BindingFlags bindingFlags
) {
    FieldInfo[] fieldInfos = type.GetFields(bindingFlags);

    // If this class doesn't have a base, don't waste any time
    if(type.BaseType == typeof(object)) {
        return fieldInfos;
    } else { // Otherwise, collect all types up to the furthest base class
        var fieldInfoList = new List<FieldInfo>(fieldInfos);
        while(type.BaseType != typeof(object)) {
            type = type.BaseType;
            fieldInfos = type.GetFields(bindingFlags);

            // Look for fields we do not have listed yet and merge them into the main list
            for(int index = 0; index < fieldInfos.Length; ++index) {
                bool found = false;

                for(int searchIndex = 0; searchIndex < fieldInfoList.Count; ++searchIndex) {
                    bool match =
                        (fieldInfoList[searchIndex].DeclaringType == fieldInfos[index].DeclaringType) &&
                        (fieldInfoList[searchIndex].Name == fieldInfos[index].Name);

                    if(match) {
                        found = true;
                        break;
                    }
                }

                if(!found) {
                    fieldInfoList.Add(fieldInfos[index]);
                }
            }
        }

        return fieldInfoList.ToArray();
    }
}

Be aware that I'm manually comparing the fields in a nested for loop. If you have deeply nested classes or monstrously big classes, feel free to use a HashSet<> instead.

EDIT: Also be aware that this doesn't search types further down in the inheritance chain. In my case I know that I'm at the most derived type when callling the method.

like image 42
Cygon Avatar answered Nov 15 '22 17:11

Cygon