GetProperties () para retornar todas as propriedades para uma hierarquia de herança de interface

96

Supondo a seguinte hierarquia de herança hipotética:

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

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

Usando reflexão e fazendo a seguinte chamada:

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

só produzirá as propriedades da interface IB, que é " Name".

Se fizéssemos um teste semelhante no código a seguir,

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

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

a chamada typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance)retornará uma matriz de PropertyInfoobjetos para " ID" e " Name".

Existe uma maneira fácil de encontrar todas as propriedades na hierarquia de herança para interfaces como no primeiro exemplo?

sduplooy
fonte

Respostas:

112

Eu ajustei o código de exemplo do @Marc Gravel em um método de extensão útil que encapsula classes e interfaces. Ele também adiciona as propriedades da interface primeiro, o que acredito ser o comportamento 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
fonte
2
Brilho puro! Obrigado, isso resolveu um problema que eu estava tendo semelhante à pergunta do operador.
Kamui
1
Suas referências a BindingFlags.FlattenHierarchy são redundantes, visto que você também está usando BindingFlags.Instance.
Chris Ward
1
Eu implementei isso, mas com um em Stack<Type>vez de um Queue<>. Com uma pilha, a ancestralidade mantém uma ordem tal que interface IFoo : IBar, IBazonde IBar : IBubblee 'IBaz: IFlubber , the order of reflection becomes: IBar , IBubble , IBaz , IFlubber , IFoo`.
IAbstract
4
Não há necessidade de recursão ou filas, pois GetInterfaces () já retorna todas as interfaces implementadas por um tipo. Conforme observado por Marc, não há hierarquia, então por que deveríamos ter que "recurse" em qualquer coisa?
glopes
3
@FrankyHollywood é por isso que você não usa GetProperties. Você usa GetInterfacesem seu tipo inicial que retornará a lista achatada de todas as interfaces e simplesmente fará GetPropertiesem cada interface. Não há necessidade de recursão. Não há herança ou tipos de base nas interfaces.
Glopes
77

Type.GetInterfaces retorna a hierarquia achatada, portanto, não há necessidade de uma descida recursiva.

Todo o método pode ser escrito de forma muito mais 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
fonte
8
Esta deve ser definitivamente a resposta certa! Não há necessidade de recursão desajeitada.
glopes de
Resposta sólida, obrigado. Como podemos obter o valor de uma propriedade na interface base?
ilker unal
1
@ilkerunal: A maneira usual: Chame GetValueo recuperado PropertyInfo, passando sua instância (cujo valor de propriedade obter) como parâmetro. Exemplo: var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list);← retornará 3, embora Countseja definido em ICollection, não IList.
Douglas
2
Essa solução tem falhas, pois pode retornar propriedades com o mesmo nome várias vezes. É necessária uma limpeza adicional dos resultados para uma lista de propriedades distinta. A resposta aceita é a solução mais correta, pois garante o retorno de propriedades com nomes exclusivos e o faz pegando aquele mais próximo na cadeia de herança.
user3524983
1
@AntWaters GetInterfaces não é necessário se typefor uma classe, porque a classe concreta DEVE implementar todas as propriedades que são definidas em todas as interfaces na cadeia de herança. O uso GetInterfacesnesse cenário resultaria na duplicação de TODAS as propriedades.
Chris Schaller
15

Hierarquias de interface são uma dor - elas realmente não "herdam" como tal, já que você pode ter vários "pais" (por falta de um termo melhor).

"Achatar" (mais uma vez, não é exatamente o termo certo) a hierarquia pode envolver a verificação de todas as interfaces que a interface implementa e o trabalho a partir delas ...

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
fonte
7
Discordo. Com todo o respeito por Marc, essa resposta também falha em perceber que GetInterfaces () já retorna todas as interfaces implementadas para um tipo. Justamente porque não há "hierarquia", não há necessidade de recursão ou filas.
glopes de
3

Exatamente o mesmo problema tem uma solução alternativa descrita aqui .

FlattenHierarchy não funciona btw. (apenas no vars. estático diz isso no intellisense)

Gambiarra. Cuidado com as duplicatas.

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

Respondendo a @douglas e @ user3524983, o seguinte deve responder à pergunta do 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();
    }

ou, para uma propriedade 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();
    }

OK da próxima vez, vou depurá-lo antes de postar em vez de depois :-)

sjb-sjb
fonte
1

isso funcionou bem e concisa para mim em um fichário de modelo MVC personalizado. Deve ser capaz de extrapolar para qualquer cenário de reflexão. Ainda fede que seja muito passado

    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
fonte