Zakładając następującą hipotetyczną hierarchię dziedziczenia:
public interface IA
{
int ID { get; set; }
}
public interface IB : IA
{
string Name { get; set; }
}
Korzystając z refleksji i wykonując następujące wezwanie:
typeof(IB).GetProperties(BindingFlags.Public | BindingFlags.Instance)
zwróci tylko właściwości interfejsu IB
, którym jest „ Name
”.
Gdybyśmy mieli wykonać podobny test na poniższym kodzie,
public abstract class A
{
public int ID { get; set; }
}
public class B : A
{
public string Name { get; set; }
}
wywołanie typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance)
zwróci tablicę PropertyInfo
obiektów dla „ ID
” i „ Name
”.
Czy istnieje łatwy sposób na znalezienie wszystkich właściwości w hierarchii dziedziczenia dla interfejsów, tak jak w pierwszym przykładzie?
c#
.net
reflection
sduplooy
źródło
źródło
Stack<Type>
rozszerzeniem zamiastQueue<>
. Ze stosem przodkowie utrzymują kolejność taką, żeinterface IFoo : IBar, IBaz
gdzieIBar : IBubble
i "IBaz: IFlubber, the order of reflection becomes:
IBar,
IBubble,
IBaz,
IFlubber,
IFoo".GetProperties
. UżywaszGetInterfaces
na swoim typie początkowym, który zwróci spłaszczoną listę wszystkich interfejsów i po prostu zrobi toGetProperties
na każdym interfejsie. Nie ma potrzeby rekursji. W interfejsach nie ma dziedziczenia ani typów podstawowych.Type.GetInterfaces
zwraca spłaszczoną hierarchię, więc nie ma potrzeby zejścia rekursywnego.Całą metodę można napisać znacznie bardziej zwięźle za pomocą 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()); }
źródło
GetValue
pobranePropertyInfo
, przekazując jako parametr swoją instancję (której wartość właściwości ma zostać pobrana ). Przykład:var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list);
← zwróci 3, mimo żeCount
jest zdefiniowane w środkuICollection
, a nieIList
.GetInterfaces
nie jest wymagane, jeślitype
jest klasą, ponieważ konkretna klasa MUSI implementować wszystkie właściwości zdefiniowane we wszystkich interfejsach w łańcuchu dziedziczenia. UżycieGetInterfaces
w tym scenariuszu spowodowałoby zduplikowanie WSZYSTKICH właściwości.Hierarchie interfejsów są uciążliwe - tak naprawdę nie „dziedziczą” jako takie, ponieważ można mieć wielu „rodziców” (z braku lepszego terminu).
„Spłaszczenie” (znowu, niezupełnie właściwy termin) hierarchia może wymagać sprawdzenia wszystkich interfejsów implementowanych przez interfejs i pracy z tego miejsca ...
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); } } } }
źródło
HashSet<Type>
forconsidered
jest lepsze niż użycieList<Type>
tutaj? Zawartość na liście ma pętlę, a ta pętla jest umieszczona w pętli foreach. Uważam, że zaszkodziłoby to wydajności, jeśli jest wystarczająco dużo elementów, a kod powinien być krytycznie szybki.Dokładnie ten sam problem ma obejście opisane tutaj .
Przy okazji FlattenHierarchy nie działa. (tylko na zmiennych statycznych. tak mówi w intelisense)
Obejście problemu. Uważaj na duplikaty.
PropertyInfo[] pis = typeof(IB).GetProperties(BindingFlags.Public | BindingFlags.Instance); Type[] tt = typeof(IB).GetInterfaces(); PropertyInfo[] pis2 = tt[0].GetProperties(BindingFlags.Public | BindingFlags.Instance);
źródło
Odpowiadając na @douglas i @ user3524983, poniższe odpowiedzi powinny odpowiedzieć na pytanie 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(); }
lub, dla indywidualnej nieruchomości:
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, następnym razem zdebuguję to przed wysłaniem zamiast po :-)
źródło
to działało ładnie i zwięźle dla mnie w segregatorze niestandardowym modelu MVC. Powinien jednak móc dokonać ekstrapolacji na dowolny scenariusz refleksji. Wciąż śmierdzi, że to przeszło
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)
źródło