GetProperties () เพื่อส่งคืนคุณสมบัติทั้งหมดสำหรับลำดับชั้นการสืบทอดอินเทอร์เฟซ


99

สมมติว่าลำดับชั้นการสืบทอดสมมุติฐานดังต่อไปนี้:

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

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

ใช้การสะท้อนและการโทรต่อไปนี้:

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

จะให้เฉพาะคุณสมบัติของอินเทอร์เฟซIBซึ่งก็คือ " Name"

หากเราจะทำการทดสอบที่คล้ายกันกับรหัสต่อไปนี้

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

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

การเรียกtypeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance)จะส่งคืนอาร์เรย์ของPropertyInfoวัตถุสำหรับ " ID" และ " Name"

มีวิธีง่ายๆในการค้นหาคุณสมบัติทั้งหมดในลำดับชั้นการสืบทอดสำหรับอินเทอร์เฟซดังตัวอย่างแรกหรือไม่

คำตอบ:


112

ฉันได้ปรับแต่งโค้ดตัวอย่างของ @Marc Gravel เป็นวิธีการขยายที่มีประโยชน์ซึ่งห่อหุ้มทั้งคลาสและอินเทอร์เฟซ นอกจากนี้ยังเพิ่มคุณสมบัติของอินเทอร์เฟซก่อนซึ่งฉันเชื่อว่าเป็นพฤติกรรมที่คาดหวัง

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
ความสดใสบริสุทธิ์! ขอบคุณสิ่งนี้ช่วยแก้ปัญหาที่ฉันมีคล้ายกับคำถามของ op
kamui

1
การอ้างอิงถึง BindingFlags.FlattenHierarchy จะซ้ำซ้อนเมื่อคุณใช้ BindingFlags.Instance
Chris Ward

1
ฉันได้ใช้สิ่งนี้แล้ว แต่Stack<Type>แทนที่จะเป็นQueue<>ไฟล์. ด้วยกองซ้อนบรรพบุรุษจะรักษาคำสั่งเช่นนั้นว่าinterface IFoo : IBar, IBazที่ไหนIBar : IBubbleและ 'IBaz: IFlubber , the order of reflection becomes: IBar , IBubble , IBaz , IFlubber , IFoo`
IAbstract

4
ไม่จำเป็นต้องมีการเรียกซ้ำหรือคิวเนื่องจาก GetInterfaces () ส่งคืนอินเทอร์เฟซทั้งหมดที่ใช้งานโดยประเภทแล้ว ตามที่ Marc ระบุไว้ไม่มีลำดับชั้นดังนั้นทำไมเราจึงต้อง "เรียกคืน" ในสิ่งใด ๆ ?
Glopes

3
@FrankyHollywood GetPropertiesนั่นเป็นเหตุผลที่คุณไม่ได้ใช้ คุณใช้GetInterfacesกับประเภทเริ่มต้นของคุณซึ่งจะส่งคืนรายการที่แบนของอินเทอร์เฟซทั้งหมดและทำGetPropertiesในแต่ละอินเทอร์เฟซ ไม่จำเป็นต้องเรียกซ้ำ ไม่มีการสืบทอดหรือประเภทพื้นฐานในอินเทอร์เฟซ
Glopes

81

Type.GetInterfaces ส่งคืนลำดับชั้นแบบแบนดังนั้นจึงไม่จำเป็นต้องมีการสืบเชื้อสายซ้ำ

วิธีการทั้งหมดสามารถเขียนได้อย่างรัดกุมยิ่งขึ้นโดยใช้ 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
นี่น่าจะเป็นคำตอบที่ใช่แน่นอน! ไม่จำเป็นต้องมีการเรียกซ้ำที่น่าเบื่อ
Glopes

คำตอบที่มั่นคงขอบคุณ เราจะได้รับมูลค่าของคุณสมบัติในอินเทอร์เฟซพื้นฐานได้อย่างไร?
ilker unal

1
@ilkerunal: วิธีปกติ: เรียกGetValueสิ่งที่ดึงมาPropertyInfoส่งผ่านอินสแตนซ์ของคุณ (ซึ่งมีค่าคุณสมบัติที่จะได้รับ) เป็นพารามิเตอร์ ตัวอย่าง: var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list);←จะกลับ 3 แม้ว่าจะCountกำหนดไว้ภายในไม่ICollection IList
Douglas

2
โซลูชันนี้มีข้อบกพร่องที่อาจส่งคืนคุณสมบัติของชื่อเดียวกันหลายครั้ง จำเป็นต้องมีการล้างผลลัพธ์เพิ่มเติมสำหรับรายการคุณสมบัติที่แตกต่างกัน คำตอบที่ได้รับการยอมรับคือคำตอบที่ถูกต้องกว่าเนื่องจากรับประกันคุณสมบัติที่ส่งคืนด้วยชื่อเฉพาะและทำได้โดยการคว้าสิ่งที่ใกล้เคียงที่สุดในห่วงโซ่การสืบทอด
user3524983

1
@AntWaters GetInterfaces ไม่จำเป็นต้องถ้าtypeเป็นชั้นเพราะชั้นคอนกรีตจะต้องดำเนินการทั้งหมดของคุณสมบัติที่กำหนดไว้ในทุกการเชื่อมต่อลงห่วงโซ่มรดก การใช้GetInterfacesในสถานการณ์นั้นจะส่งผลให้คุณสมบัติทั้งหมดซ้ำกัน
Chris Schaller

15

ลำดับชั้นของอินเทอร์เฟซเป็นความเจ็บปวด - พวกเขาไม่ได้ "สืบทอด" แบบนั้นจริงๆเนื่องจากคุณสามารถมี "ผู้ปกครอง" ได้หลายคน (เพื่อต้องการคำที่ดีกว่า)

"การทำให้แบน" (อีกครั้งไม่ใช่คำที่ถูกต้อง) ลำดับชั้นอาจเกี่ยวข้องกับการตรวจสอบอินเทอร์เฟซทั้งหมดที่อินเทอร์เฟซใช้และทำงานจากที่นั่น ...

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
ฉันไม่เห็นด้วย. ด้วยความเคารพตามสมควรสำหรับ Marc คำตอบนี้ยังไม่ทราบว่า GetInterfaces () ส่งคืนอินเทอร์เฟซที่ใช้งานทั้งหมดสำหรับประเภทแล้ว แม่นยำเนื่องจากไม่มี "ลำดับชั้น" จึงไม่จำเป็นต้องมีการเรียกซ้ำหรือคิว
glopes

ฉันสงสัยว่าการใช้HashSet<Type>for consideredดีกว่าใช้List<Type>ที่นี่หรือไม่? การมีอยู่ในรายการมีการวนซ้ำและการวนซ้ำนั้นถูกใส่ไว้ใน foreach loop ฉันเชื่อว่าจะส่งผลเสียต่อประสิทธิภาพหากมีรายการเพียงพอและโค้ดควรเร็วมาก
สิ้นหวัง

3

ว่าปัญหาเดียวกันได้วิธีแก้ปัญหาที่อธิบายไว้ที่นี่

FlattenHierarchy ไม่ทำงาน btw (เฉพาะใน vars แบบคงที่กล่าวเช่นนั้นใน Intellisense)

วิธีแก้ปัญหา ระวังรายการซ้ำ

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 และ @ user3524983 สิ่งต่อไปนี้ควรตอบคำถามของ 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();
    }

หรือสำหรับทรัพย์สินแต่ละรายการ:

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

ตกลงครั้งหน้าฉันจะแก้จุดบกพร่องก่อนโพสต์แทน after :-)


1

สิ่งนี้ใช้งานได้ดีและเหมาะสมสำหรับฉันในตัวประสานแบบจำลอง MVC ที่กำหนดเอง ควรจะสามารถคาดการณ์สถานการณ์สะท้อนใด ๆ ได้ ยังคงเหม็นที่มันผ่านเกินไป

    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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.