Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting all fields - including inherited ones - with a custom attribute

I've got a custom attribute BacksCache that I'm using to mark fields in a class that need to be "defaulted" when certain conditions are met.

I'm using the following code:

Type type = obj.GetType();
FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy);

foreach (FieldInfo field in fields)
{
  foreach (Attribute at in field.GetCustomAttributes(true))
  if (at is BacksCache)
  {
    field.SetValue(obj, Utils.DefaultValue(field.FieldType));
  }
}

This code works fine, provided that the class inheritance hierarchy is flat. That is to say, if type is the Type that declares all the attributed fields, everything's great. As soon as I have a class A from which descends class B (B : A) and B has some attributed fields things fall apart: only the fields declared by A are detected and "defaulted".

The fields in question are private or private volatile, if that makes any difference.

Any ideas on how to fix this?

like image 208
Kevin Montrose Avatar asked Dec 23 '22 06:12

Kevin Montrose


2 Answers

Beware of the small print for the BindingFlags.FlattenHierarchy option:

Specifies that public and protected static members up the hierarchy should be returned. Private static members in inherited classes are not returned. Static members include fields, methods, events, and properties. Nested types are not returned.

The "static" word in the bolded phrase is an oops, no private members are returned. To work around this you'll need to move up the inheritance chain through Type.BaseType.

like image 121
Hans Passant Avatar answered Jan 23 '23 16:01

Hans Passant


Your wording is a bit confusing, which class is the base class A or B? And which one are you reflecting over, the base class or the derived class. I ask because I'm having trouble reproducing your issue. I've tried this and it works fine:

    static void Main(string[] args)
    {

        foreach (var prop in typeof(Sub).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
        {
            foreach (var attrib in prop.GetCustomAttributes(typeof(DescriptionAttribute), false))
            {
                Console.WriteLine(prop.Name);
            }
        }

        Console.ReadKey(true);
    }


public class Base
{
    [Description]
    public int IntProp { get; set; }
}

public class Sub : Base
{
    [Description]
    public string StringProp { get; set; }
}

The output is correct, it shows both properties.

Furthermore, I just noticed you're dealing with Fields. I did this, and still got the correct output:

    static void Main(string[] args)
    {
        foreach (var prop in typeof(Sub).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
        {
            foreach (var attrib in prop.GetCustomAttributes(typeof(DescriptionAttribute), false))
            {
                Console.WriteLine(prop.Name);
            }
        }

        Console.ReadKey(true);
    }


public class Base
{
    [Description]
    public int X;

    [Description]
    public int IntProp { get; set; }
}

public class Sub : Base
{
    [Description]
    public int Y;

    [Description]
    public string StringProp { get; set; }
}

Both X and Y get output to the console.

like image 45
BFree Avatar answered Jan 23 '23 16:01

BFree