GetProperties () untuk mengembalikan semua properti untuk hierarki pewarisan antarmuka

97

Dengan asumsi hierarki pewarisan hipotetis berikut:

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

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

Menggunakan refleksi dan melakukan panggilan berikut:

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

hanya akan menghasilkan properti antarmuka IB, yaitu " Name".

Jika kami melakukan tes serupa pada kode berikut,

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

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

panggilan typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance)akan mengembalikan larik PropertyInfoobjek untuk " ID" dan " Name".

Apakah ada cara mudah untuk menemukan semua properti dalam hierarki pewarisan untuk antarmuka seperti pada contoh pertama?

sduplooy
sumber

Jawaban:

112

Saya telah mengubah kode contoh @Marc Gravel menjadi metode ekstensi yang berguna merangkum kelas dan antarmuka. Ini juga menambahkan properti antarmuka terlebih dahulu yang saya yakini adalah perilaku yang diharapkan.

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
sumber
2
Kecemerlangan Murni! Terima kasih, ini memecahkan masalah yang saya alami mirip dengan pertanyaan operasi.
kamui
1
Referensi Anda ke BindingFlags.FlattenHierarchy berlebihan karena Anda juga menggunakan BindingFlags.Instance.
Chris Ward
1
Saya telah menerapkan ini tetapi dengan Stack<Type>alih - alih a Queue<>. Dengan tumpukan, keturunan mempertahankan urutan seperti itu di interface IFoo : IBar, IBazmana IBar : IBubbledan 'IBaz: IFlubber , the order of reflection becomes: IBar , IBubble , IBaz , IFlubber , IFoo`.
IAbstract
4
Tidak perlu rekursi atau antrian karena GetInterfaces () sudah mengembalikan semua antarmuka yang diimplementasikan oleh sebuah tipe. Seperti yang dikemukakan oleh Marc, tidak ada hierarki, jadi mengapa kita harus "mengulang" apa pun?
meluncur
3
@FrankyHollywood itu sebabnya Anda tidak menggunakan GetProperties. Anda gunakan GetInterfacespada tipe awal Anda yang akan mengembalikan daftar rata dari semua antarmuka dan cukup lakukan GetPropertiespada setiap antarmuka. Tidak perlu rekursi. Tidak ada pewarisan atau tipe dasar di antarmuka.
meluncur
78

Type.GetInterfaces mengembalikan hierarki yang diratakan, jadi tidak perlu penurunan rekursif.

Seluruh metode dapat ditulis lebih ringkas menggunakan 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
sumber
8
Ini pasti jawaban yang benar! Tidak perlu rekursi yang kikuk.
meluncur
Jawaban yang solid, terima kasih. Bagaimana kita bisa mendapatkan nilai properti di antarmuka dasar?
ilker unal
1
@ilkerunal: Cara yang biasa: Panggil GetValueyang diambil PropertyInfo, meneruskan instance Anda (yang nilai propertinya akan diambil ) sebagai parameter. Contoh: var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list);← akan menghasilkan 3, meskipun Countdidefinisikan di dalam ICollection, bukan IList.
Douglas
2
Solusi ini memiliki kekurangan karena dapat mengembalikan properti dengan nama yang sama beberapa kali. Pembersihan lebih lanjut dari hasil diperlukan untuk daftar properti yang berbeda. Jawaban yang diterima adalah solusi yang lebih tepat karena menjamin properti yang dikembalikan dengan nama unik dan melakukannya dengan mengambil yang terdekat dalam rantai warisan.
pengguna3524983
1
@AntWaters GetInterfaces tidak diperlukan jika typeadalah sebuah kelas, karena kelas konkret HARUS mengimplementasikan semua properti yang ditentukan di semua Antarmuka di rantai pewarisan. Menggunakan GetInterfacesskenario itu akan mengakibatkan SEMUA properti diduplikasi.
Chris Schaller
15

Hierarki antarmuka sangat merepotkan - mereka tidak benar-benar "mewarisi" seperti itu, karena Anda dapat memiliki banyak "orang tua" (karena menginginkan istilah yang lebih baik).

"Meratakan" (sekali lagi, bukan istilah yang tepat) hierarki mungkin melibatkan pemeriksaan semua antarmuka yang diimplementasikan antarmuka dan bekerja dari sana ...

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
sumber
7
Saya tidak setuju. Dengan segala hormat untuk Marc, jawaban ini juga gagal untuk menyadari bahwa GetInterfaces () sudah mengembalikan semua antarmuka yang diimplementasikan untuk suatu tipe. Justru karena tidak ada "hierarki", tidak perlu rekursi atau antrean.
meluncur
3

Masalah yang sama persis memiliki solusi yang dijelaskan di sini .

FlattenHierarchy tidak berfungsi btw. (hanya pada vars statis. mengatakan demikian dalam intellisense)

Solusi. Waspadalah terhadap duplikat.

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

Menanggapi @douglas dan @ user3524983, pertanyaan OP berikut harus dijawab:

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

atau, untuk properti individu:

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

Oke lain kali saya akan men-debugnya sebelum memposting alih-alih setelah :-)

sjb-sjb.dll
sumber
1

ini bekerja dengan baik dan singkat bagi saya dalam pengikat model MVC kustom. Harus dapat memperkirakan skenario refleksi apa pun. Masih agak menyebalkan karena itu terlalu berlalu

    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
sumber