Arayüz devralma hiyerarşisinin tüm özelliklerini döndürmek için GetProperties ()


99

Aşağıdaki varsayımsal miras hiyerarşisini varsayarsak:

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

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

Düşünmeyi kullanmak ve aşağıdaki çağrıyı yapmak:

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

sadece IB" Name" olan arayüz özelliklerini verecektir .

Aşağıdaki kod üzerinde benzer bir test yapacak olsaydık,

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

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

çağrı typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance), PropertyInfo" ID" ve " Name" için bir dizi nesne döndürür .

İlk örnekte olduğu gibi arayüzler için kalıtım hiyerarşisindeki tüm özellikleri bulmanın kolay bir yolu var mı?

Yanıtlar:


111

@Marc Gravel'in örnek kodunu, hem sınıfları hem de arayüzleri kapsayan kullanışlı bir genişletme yöntemine dönüştürdüm. Ayrıca, beklenen davranış olduğuna inandığım ilk önce arayüz özelliklerini de ekler.

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
Saf Parlaklık! Teşekkürler bu, operasyonun sorusuna benzer bir problemi çözdü.
kamui

1
BindingFlags.FlattenHierarchy'ye olan referanslarınız, BindingFlags.Instance'ı da kullandığınızı görmek gereksizdir.
Chris Ward

1
Bunu uyguladım ama a Stack<Type>yerine a ile Queue<>. Bir yığınla soy , interface IFoo : IBar, IBaznerede IBar : IBubbleve 'IBaz: IFlubber , the order of reflection becomes: IBar , IBubble , IBaz , IFlubber , IFoo` şeklinde bir düzen sağlar.
IAbstract

4
GetInterfaces () bir tür tarafından uygulanan tüm arabirimleri zaten döndürdüğü için özyineleme veya kuyruklara gerek yoktur. Marc'ın belirttiği gibi, hiyerarşi yoktur, öyleyse neden herhangi bir şeyi "yinelemeliyiz"?
glopes

3
@FrankyHollywood bu yüzden kullanmıyorsun GetProperties. Sen kullanmak GetInterfacestüm arayüzleri düzleştirilmiş listesini döndürür ve yapılacak basitçe hangi sizin başlangıç türüne GetPropertiesher arabirimde. Özyinelemeye gerek yok. Arayüzlerde kalıtım veya temel tip yoktur.
2016

80

Type.GetInterfaces düzleştirilmiş hiyerarşiyi döndürür, böylece yinelemeli bir inişe gerek yoktur.

Tüm yöntem LINQ kullanılarak çok daha kısaca yazılabilir:

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
Bu kesinlikle doğru cevap olmalı! Hantal yinelemeye gerek yok.
glopes

Sağlam cevap teşekkür ederim. Temel arayüzde bir özelliğin değerini nasıl elde edebiliriz?
ilker unal

1
@ilkerunal: Normal yol: GetValueAlınan üzerinde çağrı PropertyInfoyapın, örneğinizi (özellik değeri alınacak ) parametre olarak iletin . Örnek: var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list);← rağmen 3 dönecektir Countiçinde tanımlanır ICollectiondeğil IList.
Douglas

2
Bu çözümün, aynı isimdeki özellikleri birden çok kez döndürebilmesi gibi kusurları vardır. Farklı bir mülk listesi için sonuçların daha fazla temizlenmesi gerekir. Kabul edilen cevap, benzersiz adlara sahip mülklerin iade edilmesini garanti ettiği ve bunu kalıtım zincirinde en yakın olanı alarak yaptığı için daha doğru çözümdür.
user3524983

1
@AntWaters GetInterfaces eğer gerekli değildir typebir sınıftır beton sınıfı, çünkü GEREKİR uygulamak tüm miras zincirin üzerindeki tüm arabirimleri tanımlanan özelliklerin. GetInterfacesBu senaryoda kullanmak TÜM özelliklerin çoğaltılmasına neden olur.
Chris Schaller

15

Arayüz hiyerarşileri bir sıkıntıdır - birden fazla "ebeveyn" e sahip olabileceğiniz için (daha iyi bir terim istemek için) gerçekten "miras almazlar".

"Düzleştirme" (yine, tam olarak doğru bir terim değil) hiyerarşi, arayüzün uyguladığı tüm arayüzlerin kontrol edilmesini ve buradan çalışılmasını içerebilir ...

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
Katılmıyorum. Marc'a tüm saygımla, bu cevap aynı zamanda GetInterfaces () 'ın bir tür için uygulanan tüm arabirimleri zaten döndürdüğünü anlamıyor. Kesin olarak "hiyerarşi" olmadığı için, özyinelemeye veya kuyruklara gerek yoktur.
glopes

Acaba HashSet<Type>for consideredkullanmanın List<Type>burada kullanmaktan daha iyi olup olmadığını merak ediyorum. Bir Listede İçeren bir döngüye sahiptir ve bu döngü bir foreach döngüsüne yerleştirilir, yeterli öğe varsa ve kodun kritik derecede hızlı olması gerekiyorsa bunun performansa zarar vereceğine inanıyorum.
Umutsuz

3

Tam olarak aynı sorunun burada açıklanan bir çözümü vardır .

FlattenHierarchy btw çalışmıyor. (sadece statik değişkenlerde intellisense'de öyle diyor)

Geçici çözüm. Yinelemelere dikkat edin.

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

2

@Douglas ve @ user3524983'e yanıt verirken, aşağıdakiler OP'nin sorusuna yanıt vermelidir:

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

veya bireysel bir mülk için:

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

Tamam bir dahaki sefere sonrası yerine göndermeden önce hatalarını ayıklayacağım :-)


1

bu benim için özel bir MVC model bağlayıcısında güzelce ve kısaca çalıştı. Yine de herhangi bir yansıma senaryosuna ekstrapole edebilmelidir. Hala çok geçtiği için kokuyor

    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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.