views:

120

answers:

3

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?

A: 

Attribute.GetCustomAttributes will search for inherited values. The code to do so given your example would be:

foreach (Attribute at in Attribute.GetCustomAttributes(field, true))
{
    // Test and set here
}

For further explanation see Weitao Su's post on GetCustomAttributes.

Michael Greene
+2  A: 

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.

BFree
+1: Sorry about the ambiguities, your answer is correct for one interpretation of the question as originally asked.
Kevin Montrose
+1  A: 

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.

Hans Passant