GetProperties()返回接口继承层次结构的所有属性

假设下列假设的继承层次:

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


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

利用反思并做出以下呼吁:

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

只能产生接口 IB的属性,即“ Name”。

如果我们对以下代码进行类似的测试,

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


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

调用 typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance)将返回“ ID”和“ Name”的 PropertyInfo对象数组。

是否有一种简单的方法可以像第一个示例那样找到接口的继承层次结构中的所有属性?

53877 次浏览

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);
}
}
}
}

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);

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);
}

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)

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());
}

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 :-)