GetProperties () para devolver todas las propiedades de una jerarquía de herencia de interfaz

96

Suponiendo la siguiente jerarquía de herencia hipotética:

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

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

Usando la reflexión y haciendo la siguiente llamada:

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

solo producirá las propiedades de la interfaz IB, que es " Name".

Si hiciéramos una prueba similar en el siguiente código,

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

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

la llamada typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance)devolverá una matriz de PropertyInfoobjetos para " ID" y " Name".

¿Existe una manera fácil de encontrar todas las propiedades en la jerarquía de herencia para interfaces como en el primer ejemplo?

sduplooy
fuente

Respuestas:

112

He modificado el código de ejemplo de @Marc Gravel en un método de extensión útil que encapsula tanto las clases como las interfaces. También agrega las propiedades de la interfaz primero, que creo que es el comportamiento esperado.

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);
}
mito
fuente
2
¡Pura brillantez! Gracias, esto resolvió un problema que estaba teniendo similar a la pregunta de la operación.
Kamui
1
Tus referencias a BindingFlags.FlattenHierarchy son redundantes, ya que también estás usando BindingFlags.Instance.
Chris Ward
1
Implementé esto pero con un en Stack<Type>lugar de un Queue<>. Con una pila, la ascendencia mantiene un orden tal que interface IFoo : IBar, IBazdonde IBar : IBubbley 'IBaz: IFlubber , the order of reflection becomes: IBar , IBubble , IBaz , IFlubber , IFoo`.
IAbstract
4
No hay necesidad de recursividad o colas ya que GetInterfaces () ya devuelve todas las interfaces implementadas por un tipo. Como señaló Marc, no hay jerarquía, entonces, ¿por qué deberíamos "recurrir" a algo?
glopes
3
@FrankyHollywood es por eso que no usas GetProperties. Utiliza GetInterfacesen su tipo de inicio que devolverá la lista plana de todas las interfaces y simplemente lo hará GetPropertiesen cada interfaz. No hay necesidad de recursividad. No hay herencia ni tipos base en las interfaces.
glopes
77

Type.GetInterfaces devuelve la jerarquía aplanada, por lo que no hay necesidad de un descenso recursivo.

El método completo se puede escribir de manera mucho más concisa usando 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());
}
Douglas
fuente
8
¡Esta debería ser definitivamente la respuesta correcta! No hay necesidad de la torpe recursividad.
glopes
Respuesta sólida gracias. ¿Cómo podemos obtener el valor de una propiedad en la interfaz base?
ilker unal
1
@ilkerunal: La forma habitual: llamar GetValueal recuperado PropertyInfo, pasando su instancia (cuyo valor de propiedad obtener) como parámetro. Ejemplo: var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list);← devolverá 3, aunque Countesté definido dentro ICollection, no IList.
Douglas
2
Esta solución tiene fallas porque puede devolver propiedades con el mismo nombre varias veces. Se necesita una mayor limpieza de los resultados para una lista de propiedades distinta. La respuesta aceptada es la solución más correcta, ya que garantiza la devolución de propiedades con nombres únicos y lo hace tomando el más cercano en la cadena de herencia.
user3524983
1
@AntWaters GetInterfaces no es necesario si typees una clase, porque la clase concreta DEBE implementar todas las propiedades que están definidas en todas las interfaces a lo largo de la cadena de herencia. El uso GetInterfacesen ese escenario daría como resultado la duplicación de TODAS las propiedades.
Chris Schaller
15

Las jerarquías de interfaz son una molestia, en realidad no "heredan" como tales, ya que puede tener varios "padres" (a falta de un término mejor).

"Aplanar" (de nuevo, no es el término correcto) la jerarquía puede implicar la comprobación de todas las interfaces que implementa la interfaz y trabajar desde allí ...

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);
            }
        }
    }
}
Marc Gravell
fuente
7
Estoy en desacuerdo. Con el debido respeto por Marc, esta respuesta tampoco se da cuenta de que GetInterfaces () ya devuelve todas las interfaces implementadas para un tipo. Precisamente porque no hay una "jerarquía", no hay necesidad de recursividad o colas.
glopes
3

Exactamente el mismo problema tiene una solución que se describe aquí .

FlattenHierarchy no funciona por cierto. (solo en variables estáticas. lo dice en intellisense)

Solución alterna. Tenga cuidado con los duplicados.

PropertyInfo[] pis = typeof(IB).GetProperties(BindingFlags.Public | BindingFlags.Instance);
Type[] tt = typeof(IB).GetInterfaces();
PropertyInfo[] pis2 = tt[0].GetProperties(BindingFlags.Public | BindingFlags.Instance);
Wolf5
fuente
2

Respondiendo a @douglas y @ user3524983, lo siguiente debería responder a la pregunta del OP:

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

o, para una propiedad individual:

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

De acuerdo, la próxima vez lo depuraré antes de publicar en lugar de después :-)

sjb-sjb
fuente
1

esto funcionó muy bien y concisamente para mí en una carpeta de modelos MVC personalizada. Sin embargo, debería poder extrapolar a cualquier escenario de reflexión. Todavía apesta que es demasiado pasado

    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)
Derek Strickland
fuente