GetProperties () pour renvoyer toutes les propriétés d'une hiérarchie d'héritage d'interface

96

En supposant la hiérarchie d'héritage hypothétique suivante:

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

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

Utiliser la réflexion et faire l'appel suivant:

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

ne donnera que les propriétés de l'interface IB, qui est " Name".

Si nous devions faire un test similaire sur le code suivant,

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

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

l'appel typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance)renverra un tableau d' PropertyInfoobjets pour " ID" et " Name".

Existe-t-il un moyen simple de trouver toutes les propriétés dans la hiérarchie d'héritage pour les interfaces comme dans le premier exemple?

sduplooy
la source

Réponses:

112

J'ai modifié l'exemple de code de @Marc Gravel en une méthode d'extension utile qui encapsule à la fois les classes et les interfaces. Il ajoute également les propriétés de l'interface en premier, ce qui, je crois, est le comportement attendu.

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);
}
Mythz
la source
2
Pure Brilliance! Merci, cela a résolu un problème que j'avais similaire à la question de l'op.
kamui
1
Vos références à BindingFlags.FlattenHierarchy sont redondantes car vous utilisez également BindingFlags.Instance.
Chris Ward du
1
J'ai implémenté cela mais avec un Stack<Type>au lieu d'un Queue<>. Avec une pile, l'ascendance maintient un ordre tel que interface IFoo : IBar, IBazIBar : IBubbleet «IBaz: IFlubber , the order of reflection becomes: IBar , IBubble , IBaz , IFlubber , IFoo».
IAbstract
4
Il n'y a pas besoin de récursivité ou de files d'attente puisque GetInterfaces () retourne déjà toutes les interfaces implémentées par un type. Comme le note Marc, il n'y a pas de hiérarchie, alors pourquoi devrions-nous «récurer» sur quoi que ce soit?
glopes
3
@FrankyHollywood c'est pourquoi vous n'utilisez pas GetProperties. Vous utilisez GetInterfacessur votre type de départ qui retournera la liste aplatie de toutes les interfaces et faites simplement GetPropertiessur chaque interface. Pas besoin de récursivité. Il n'y a pas d'héritage ou de types de base dans les interfaces.
glopes
77

Type.GetInterfaces renvoie la hiérarchie aplatie, il n'y a donc pas besoin d'une descente récursive.

La méthode entière peut être écrite de manière beaucoup plus concise en utilisant 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
la source
8
Cela devrait certainement être la bonne réponse! Pas besoin de la récursivité maladroite.
glopes
Réponse solide merci. Comment pouvons-nous obtenir la valeur d'une propriété dans l'interface de base?
ilker unal
1
@ilkerunal: La manière habituelle: Appelez GetValuele récupéré PropertyInfo, en passant votre instance (dont la valeur de propriété à obtenir) en paramètre. Exemple: var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list);← renverra 3, même s'il Countest défini dans ICollection, non IList.
Douglas
2
Cette solution présente des défauts en ce qu'elle peut renvoyer des propriétés du même nom plusieurs fois. Un nettoyage supplémentaire des résultats est nécessaire pour une liste de propriétés distincte. La réponse acceptée est la solution la plus correcte car elle garantit le retour des propriétés avec des noms uniques et le fait en saisissant celle la plus proche de la chaîne d'héritage.
user3524983
1
@AntWaters GetInterfaces n'est pas requis si le typeest une classe, car la classe concrète DOIT implémenter toutes les propriétés qui sont définies dans toutes les interfaces en aval de la chaîne d'héritage. L'utilisation GetInterfacesdans ce scénario entraînerait la duplication de TOUTES les propriétés.
Chris Schaller
15

Les hiérarchies d'interface sont pénibles - elles n'héritent pas vraiment en tant que telles, puisque vous pouvez avoir plusieurs «parents» (faute d'un meilleur terme).

"Aplatir" (encore une fois, pas tout à fait le bon terme) la hiérarchie peut impliquer de vérifier toutes les interfaces implémentées par l'interface et de travailler à partir de là ...

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
la source
7
Je ne suis pas d'accord. Avec tout le respect que je dois à Marc, cette réponse ne permet pas non plus de réaliser que GetInterfaces () renvoie déjà toutes les interfaces implémentées pour un type. Précisément parce qu'il n'y a pas de "hiérarchie", il n'y a pas besoin de récursivité ou de files d'attente.
glopes
3

Exactement le même problème a une solution de contournement décrite ici .

FlattenHierarchy ne fonctionne pas btw. (uniquement sur les variables statiques. le dit en intellisense)

Solution de contournement. Méfiez-vous des doublons.

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

En réponse à @douglas et @ user3524983, les éléments suivants devraient répondre à la question du PO:

    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, pour une propriété individuelle:

    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 la prochaine fois, je le déboguerai avant de publier au lieu après :-)

sjb-sjb
la source
1

cela a bien fonctionné pour moi dans un classeur de modèle MVC personnalisé. Devrait être capable d'extrapoler à n'importe quel scénario de réflexion. Encore un peu pue que c'est trop passer

    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
la source