GetProperties () per restituire tutte le proprietà per una gerarchia di ereditarietà dell'interfaccia


96

Supponendo la seguente gerarchia di ereditarietà ipotetica:

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

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

Usando la riflessione e facendo la seguente chiamata:

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

restituirà solo le proprietà dell'interfaccia IB, che è " Name".

Se dovessimo fare un test simile sul seguente codice,

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

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

la chiamata typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance)restituirà un array di PropertyInfooggetti per " ID" e " Name".

Esiste un modo semplice per trovare tutte le proprietà nella gerarchia di ereditarietà per le interfacce come nel primo esempio?

Risposte:


112

Ho modificato il codice di esempio di @Marc Gravel in un utile metodo di estensione che incapsula sia le classi che le interfacce. Inoltre aggiunge prima le proprietà dell'interfaccia che credo sia il comportamento previsto.

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

2
Pure Brilliance! Grazie, questo ha risolto un problema che stavo avendo simile alla domanda dell'operazione.
kamui

1
I tuoi riferimenti a BindingFlags.FlattenHierarchy sono ridondanti visto che stai usando anche BindingFlags.Instance.
Chris Ward,

1
L'ho implementato ma con un Stack<Type>invece di un Queue<>. Con una pila, la discendenza mantiene un ordine tale che interface IFoo : IBar, IBazwhere IBar : IBubblee "IBaz: IFlubber , the order of reflection becomes: IBar , IBubble , IBaz , IFlubber , IFoo".
IAbstract

4
Non è necessaria la ricorsione o le code poiché GetInterfaces () restituisce già tutte le interfacce implementate da un tipo. Come notato da Marc, non esiste una gerarchia, quindi perché dovremmo "ricorrere" a qualcosa?
guanti l'

3
@FrankyHollywood è per questo che non usi GetProperties. Si utilizza GetInterfacessul tipo di partenza che restituirà l'elenco appiattito di tutte le interfacce e lo farà semplicemente GetPropertiessu ciascuna interfaccia. Non c'è bisogno di ricorsione. Non ci sono ereditarietà o tipi di base nelle interfacce.
guanti

77

Type.GetInterfaces restituisce la gerarchia appiattita, quindi non è necessaria una discesa ricorsiva.

L'intero metodo può essere scritto in modo molto più conciso utilizzando 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());
}

8
Questa dovrebbe essere sicuramente la risposta giusta! Non c'è bisogno della goffa ricorsione.
guanti l'

Risposta solida grazie. Come possiamo ottenere il valore di una proprietà nell'interfaccia di base?
ilker unal

1
@ilkerunal: il solito modo: chiama GetValueil recuperato PropertyInfo, passando la tua istanza (di cui ottenere il valore della proprietà) come parametro. Esempio: var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list);← restituirà 3, anche se Countè definito all'interno ICollection, non IList.
Douglas

2
Questa soluzione presenta difetti in quanto può restituire più volte proprietà con lo stesso nome. È necessaria un'ulteriore pulizia dei risultati per un elenco di proprietà distinto. La risposta accettata è la soluzione più corretta in quanto garantisce la restituzione di proprietà con nomi univoci e lo fa afferrando quella più vicina nella catena di ereditarietà.
user3524983

1
@AntWaters GetInterfaces non è richiesto se typeè una classe, perché la classe concreta DEVE implementare tutte le proprietà definite in tutte le interfacce lungo la catena di ereditarietà. L'utilizzo GetInterfacesin quello scenario comporterebbe la duplicazione di TUTTE le proprietà.
Chris Schaller

15

Le gerarchie di interfaccia sono un problema: non "ereditano" in quanto tali, poiché puoi avere più "genitori" (in mancanza di un termine migliore).

"Appiattire" (di nuovo, non è il termine giusto) la gerarchia potrebbe comportare il controllo di tutte le interfacce che l'interfaccia implementa e il lavoro da 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);
            }
        }
    }
}

7
Non sono d'accordo. Con tutto il rispetto per Marc, anche questa risposta non si rende conto che GetInterfaces () restituisce già tutte le interfacce implementate per un tipo. Proprio perché non c'è "gerarchia", non c'è bisogno di ricorsione o code.
guanti l'

3

Esattamente lo stesso problema ha una soluzione alternativa descritta qui .

FlattenHierarchy non funziona btw. (solo su variabili statiche. dice così in intellisense)

Soluzione. Attenzione ai duplicati.

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

2

Rispondendo a @douglas e @ user3524983, quanto segue dovrebbe rispondere alla domanda dell'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();
    }

oppure, per una singola proprietà:

    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 prossima volta che eseguirò il debug prima di postare invece che dopo :-)


1

questo ha funzionato bene e in modo conciso per me in un raccoglitore di modelli MVC personalizzato. Dovrebbe essere in grado di estrapolare qualsiasi scenario di riflessione però. Ancora una specie di puzza che sia troppo passata

    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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.