GetProperties() to return all properties for an interface inheritance hierarchy
Asked Answered
T

6

111

Assuming the following hypothetical inheritance hierarchy:

public interface IA
{
  int ID { get; set; }
}

public interface IB : IA
{
  string Name { get; set; }
}

Using reflection and making the following call:

typeof(IB).GetProperties(BindingFlags.Public | BindingFlags.Instance) 

will only yield the properties of interface IB, which is "Name".

If we were to do a similar test on the following code,

public abstract class A
{
  public int ID { get; set; }
}

public class B : A
{
  public string Name { get; set; }
}

the call typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance) will return an array of PropertyInfo objects for "ID" and "Name".

Is there an easy way to find all the properties in the inheritance hierarchy for interfaces as in the first example?

Tawny answered 11/12, 2008 at 9:51 Comment(1)
What I'm wondering is why the GetProperties() method behaves like this. I assume it was implemented like this intentionally. But why?Passementerie
R
116

I've tweaked @Marc Gravel's example code into a useful extension method encapsulates both classes and interfaces. It also add's the interface properties first which I believe is the expected behaviour.

public static PropertyInfo[] GetPublicProperties(this Type type)
{
    if (type.IsInterface)
    {
        var propertyInfos = new List<PropertyInfo>();

        var considered = new List<Type>();
        var queue = new Queue<Type>();
        considered.Add(type);
        queue.Enqueue(type);
        while (queue.Count > 0)
        {
            var subType = queue.Dequeue();
            foreach (var subInterface in subType.GetInterfaces())
            {
                if (considered.Contains(subInterface)) continue;

                considered.Add(subInterface);
                queue.Enqueue(subInterface);
            }

            var typeProperties = subType.GetProperties(
                BindingFlags.FlattenHierarchy 
                | BindingFlags.Public 
                | BindingFlags.Instance);

            var newPropertyInfos = typeProperties
                .Where(x => !propertyInfos.Contains(x));

            propertyInfos.InsertRange(0, newPropertyInfos);
        }

        return propertyInfos.ToArray();
    }

    return type.GetProperties(BindingFlags.FlattenHierarchy
        | BindingFlags.Public | BindingFlags.Instance);
}
Rodrigorodrigue answered 14/3, 2010 at 22:36 Comment(8)
Pure Brilliance! Thank you this solved a problem I was having similar to the op's question.Swartz
Your references to BindingFlags.FlattenHierarchy are redundant seeing as you're also using BindingFlags.Instance.Leslie
I've implemented this but with a Stack<Type> instead of a Queue<>. With a stack, the ancestry maintains an order such that interface IFoo : IBar, IBaz where IBar : IBubble and 'IBaz : IFlubber, the order of reflection becomes: IBar, IBubble, IBaz, IFlubber, IFoo`.Kiyokokiyoshi
There is no need for recursion or queues since GetInterfaces() already returns all of the interfaces implemented by a type. As noted by Marc, there is no hierarchy, so why should we have to "recurse" on anything?Matlick
@glopes, because properties in basetypes of interfaces are not returned by 'GetProperties'. Not even with the 'FlattenHierarchy' flag.Hackler
@Hackler that's why you don't use GetProperties. You use GetInterfaces on your starting type which will return the flattened list of all interfaces and simply do GetProperties on each interface. No need for recursion. There is no inheritance or base types in interfaces.Matlick
@Matlick Would you mind posting your solution as an answer?Mcknight
@ChrisWard No, BindingFlags.FlattenHierarchy specifies static members, and BindingFlags.Instance specifies instance members. The two are mutually exclusive.Mcknight
T
108

Type.GetInterfaces returns the flattened hierarchy, so there is no need for a recursive descent.

The entire method can be written much more concisely using LINQ:

public static IEnumerable<PropertyInfo> GetPublicProperties(this Type type)
{
    if (!type.IsInterface)
        return type.GetProperties();

    return (new Type[] { type })
           .Concat(type.GetInterfaces())
           .SelectMany(i => i.GetProperties());
}
Thence answered 5/11, 2014 at 20:13 Comment(10)
This should definitely be the right answer! No need for the clunky recursion.Matlick
Solid answer thank you. How can we get value of a property in base interface?Condottiere
@ilkerunal: The usual way: Call GetValue on the retrieved PropertyInfo, passing your instance (whose property value to get) as parameter. Example: var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list); ← will return 3, even though Count is defined within ICollection, not IList.Thence
This solution has flaws in that it may return properties of the same name multiple times. Further cleanup of the results are needed for a distinct property list. The accepted answer is the more correct solution as it guarantees returning properties with unique names and does so by grabbing the one closest in the inheritance chain.Rectangle
@user3524983: Could you please give an example? I just tried it and only got one property for a given name.Thence
interface Base { string String { get; } } interface Derived : Base { string String { get; } } var propertyInfos = GetPublicProperties(typeof(Derived)); foreach (var propertyInfo in propertyInfos) Console.WriteLine("Name: " + propertyInfo.Name);Mcknight
@Thence I think that is what @Rectangle is referring to. Name: String is printed twice. However, the same happens with the accepted solution...Mcknight
@Jeff: Thanks for clarifying. However, that would be the expected behavior, since those are two distinct properties that share the same Name (but different DeclaringType). Even the compiler warns that Derived.String hides inherited member Base.String. An implementing type is free to provide different explicit implementations for the two, so it would be misleading to return just one. public class Implementation : Derived { string Base.String => "B"; string Derived.String => "D"; }Thence
Does anyone know what this will return if type is a class that inherits interfaces (perhaps further up the hierarchy)? Is there a need to call GetInterfaces even for a class?Shelves
@AntWaters GetInterfaces is not required if the type is a class, because the concrete class MUST implement all of the properties that are defined in all the Interfaces down the inheritance chain. Using GetInterfaces in that scenario would result in ALL properties being duplicated.Margrettmarguerie
T
15

Interface hierarchies are a pain - they don't really "inherit" as such, since you can have multiple "parents" (for want of a better term).

"Flattening" (again, not quite the right term) the hierarchy might involve checking for all the interfaces that the interface implements and working from there...

interface ILow { void Low();}
interface IFoo : ILow { void Foo();}
interface IBar { void Bar();}
interface ITest : IFoo, IBar { void Test();}

static class Program
{
    static void Main()
    {
        List<Type> considered = new List<Type>();
        Queue<Type> queue = new Queue<Type>();
        considered.Add(typeof(ITest));
        queue.Enqueue(typeof(ITest));
        while (queue.Count > 0)
        {
            Type type = queue.Dequeue();
            Console.WriteLine("Considering " + type.Name);
            foreach (Type tmp in type.GetInterfaces())
            {
                if (!considered.Contains(tmp))
                {
                    considered.Add(tmp);
                    queue.Enqueue(tmp);
                }
            }
            foreach (var member in type.GetMembers())
            {
                Console.WriteLine(member.Name);
            }
        }
    }
}
Trichomonad answered 11/12, 2008 at 10:2 Comment(2)
I disagree. With all due respect for Marc, this answer also fails to realize that GetInterfaces() already returns all of the implemented interfaces for a type. Precisely because there is no "hierarchy", there is no need for recursion or queues.Matlick
I wonder if using a HashSet<Type> for considered is better than using List<Type> here? Contains on a List has a loop, and that loop is put in a foreach loop, I believe that would hurt the performance if there are enough items and the code should be critically fast.Satterfield
A
4

Exactly the same problem has a workaround described here.

FlattenHierarchy doesnt work btw. (only on static vars. says so in intellisense)

Workaround. Beware of duplicates.

PropertyInfo[] pis = typeof(IB).GetProperties(BindingFlags.Public | BindingFlags.Instance);
Type[] tt = typeof(IB).GetInterfaces();
PropertyInfo[] pis2 = tt[0].GetProperties(BindingFlags.Public | BindingFlags.Instance);
Anachronous answered 11/12, 2008 at 10:6 Comment(0)
E
2

Responding to @douglas and @user3524983, the following should answer the OP's question:

    static public IEnumerable<PropertyInfo> GetPropertiesAndInterfaceProperties(this Type type, BindingFlags bindingAttr = BindingFlags.Public | BindingFlags.Instance)
    {
        if (!type.IsInterface) {
            return type.GetProperties( bindingAttr);
        }

        return type.GetInterfaces().Union(new Type[] { type }).SelectMany(i => i.GetProperties(bindingAttr)).Distinct();
    }

or, for an individual property:

    static public PropertyInfo GetPropertyOrInterfaceProperty(this Type type, string propertyName, BindingFlags bindingAttr = BindingFlags.Public|BindingFlags.Instance)
    {
        if (!type.IsInterface) {
            return type.GetProperty(propertyName, bindingAttr);
        }

        return type.GetInterfaces().Union(new Type[] { type }).Select(i => i.GetProperty( propertyName, bindingAttr)).Distinct().Where(propertyInfo => propertyInfo != null).Single();
    }

OK next time I'll debug it before posting instead of after :-)

Eme answered 14/11, 2017 at 4:13 Comment(0)
R
1

this worked nicely and tersely for me in a custom MVC model binder. Should be able to extrapolate to any reflection scenario though. Still kind of stinks that it's too pass

    var props =  bindingContext.ModelType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance).ToList();

    bindingContext.ModelType.GetInterfaces()
                      .ToList()
                      .ForEach(i => props.AddRange(i.GetProperties()));

    foreach (var property in props)
Refinement answered 14/12, 2012 at 14:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.