แจกแจงคลาสทั้งหมดด้วยแอตทริบิวต์คลาสที่กำหนดเองได้อย่างไร


151

คำถามที่อยู่บนพื้นฐานของMSDN ตัวอย่างเช่น

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

คำตอบ:


205

ใช่แล้ว ใช้การสะท้อน:

static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly) {
    foreach(Type type in assembly.GetTypes()) {
        if (type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0) {
            yield return type;
        }
    }
}

7
เห็นด้วย แต่ในกรณีนี้เราสามารถทำได้อย่างชัดเจนตามวิธีการแก้ปัญหาของ casperOne มันเป็นเรื่องดีที่จะสามารถใช้อัตราผลตอบแทนก็จะยิ่งดีกว่าที่จะไม่ต้อง :)
จอนสกีต

9
ฉันชอบ LINQ รักจริง แต่มันขึ้นอยู่กับ. NET 3.5 ซึ่งผลตอบแทนไม่ได้ นอกจากนี้ LINQ ในที่สุดก็หยุดลงไปที่สิ่งเดียวกันเป็นผลตอบแทน คุณได้อะไรมา? ไวยากรณ์ C # เฉพาะนั่นคือการกำหนดค่าตามความชอบ
Andrew Arnott

1
@AndrewArnott รหัสที่สั้นและสั้นที่สุดไม่เกี่ยวข้องกับประสิทธิภาพพวกเขาเป็นเพียงผู้มีส่วนร่วมที่เป็นไปได้ในการอ่านและบำรุงรักษา ฉันท้าทายคำแถลงว่าพวกเขาจัดสรรวัตถุที่น้อยที่สุดและประสิทธิภาพจะเร็วขึ้น (โดยเฉพาะอย่างยิ่งโดยไม่มีการพิสูจน์เชิงประจักษ์); คุณได้เขียนSelectวิธีการขยายโดยทั่วไปแล้วคอมไพเลอร์จะสร้างเครื่องสถานะเช่นเดียวกับถ้าคุณเรียกว่าSelectเนื่องจากการใช้งานของyield returnคุณ ในที่สุดการเพิ่มประสิทธิภาพใด ๆ ที่อาจได้รับในกรณีส่วนใหญ่เป็นการเพิ่มประสิทธิภาพแบบไมโคร
casperOne

1
ค่อนข้างเหมาะสม @casperOne ความแตกต่างเล็กน้อยโดยเฉพาะอย่างยิ่งเมื่อเทียบกับน้ำหนักของการสะท้อน อาจจะไม่เกิดขึ้นในการติดตามที่สมบูรณ์แบบ
Andrew Arnott

1
แน่นอน Resharper บอกว่า "วง foreach สามารถแปลงเป็นนิพจน์ LINQ" ซึ่งมีลักษณะดังนี้: assembly.GetTypes () โดยที่ (type => type.GetCustomAttributes (typeof (HelpAttribute), true). Long> 0);
David Barrows

107

คุณจะต้องระบุคลาสทั้งหมดในชุดประกอบทั้งหมดที่โหลดลงในโดเมนแอปปัจจุบัน ในการทำเช่นนั้นคุณจะต้องเรียกใช้GetAssembliesเมธอดบนAppDomainอินสแตนซ์ของโดเมนแอปปัจจุบัน

จากนั้นคุณจะโทรGetExportedTypes(ถ้าคุณต้องการประเภทสาธารณะ) หรือGetTypesในแต่ละAssemblyประเภทที่จะได้รับประเภทที่มีอยู่ในการชุมนุม

จากนั้นคุณจะเรียกใช้GetCustomAttributesวิธีการขยายในแต่ละTypeอินสแตนซ์ผ่านประเภทของแอททริบิวที่คุณต้องการค้นหา

คุณสามารถใช้ LINQ เพื่อทำให้สิ่งนี้ง่ายขึ้นสำหรับคุณ:

var typesWithMyAttribute =
    from a in AppDomain.CurrentDomain.GetAssemblies()
    from t in a.GetTypes()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

ข้อความค้นหาด้านบนจะให้คุณได้รับคุณลักษณะแต่ละประเภทพร้อมกับแอตทริบิวต์ที่คุณใช้พร้อมกับตัวอย่างของแอตทริบิวต์ที่กำหนดไว้

โปรดทราบว่าถ้าคุณมีแอสเซมบลีจำนวนมากโหลดลงในโดเมนแอปพลิเคชันของคุณการดำเนินการนั้นอาจมีราคาแพง คุณสามารถใช้Parallel LINQเพื่อลดเวลาของการดำเนินการเช่น:

var typesWithMyAttribute =
    // Note the AsParallel here, this will parallelize everything after.
    from a in AppDomain.CurrentDomain.GetAssemblies().AsParallel()
    from t in a.GetTypes()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

การกรองเฉพาะเจาะจงAssemblyนั้นง่าย:

Assembly assembly = ...;

var typesWithMyAttribute =
    from t in assembly.GetTypes()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

และถ้าแอสเซมบลีมีชนิดจำนวนมากอยู่ในนั้นคุณสามารถใช้ Parallel LINQ อีกครั้ง:

Assembly assembly = ...;

var typesWithMyAttribute =
    // Partition on the type list initially.
    from t in assembly.GetTypes().AsParallel()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

1
การแจกแจงทุกประเภทในแอสเซมบลีที่โหลดทั้งหมดจะช้ามากและไม่ได้รับมาก นอกจากนี้ยังอาจมีความเสี่ยงด้านความปลอดภัย คุณสามารถคาดเดาว่าชุดประกอบใดจะมีประเภทที่คุณสนใจเพียงระบุประเภทในประเภทเหล่านั้น
Andrew Arnott

@Andrew Arnott: ถูกต้อง แต่นี่คือสิ่งที่ถูกขอ มันง่ายพอที่จะตัดแบบสอบถามสำหรับชุดประกอบเฉพาะ สิ่งนี้ยังมีประโยชน์เพิ่มเติมในการให้คุณทำแผนที่ระหว่างประเภทและแอตทริบิวต์
casperOne

1
คุณสามารถใช้รหัสเดียวกันในแอสเซมบลีปัจจุบันด้วย System.Reflection.Assembly.GetExecutingAssembly ()
Chris Moschini

@ChrisMoschini ใช่คุณทำได้ แต่คุณอาจไม่ต้องการสแกนชุดประกอบปัจจุบันเสมอไป ดีกว่าที่จะปล่อยให้มันเปิด
casperOne

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

34

คำตอบอื่น ๆ อ้างอิงGetCustomAttributes การเพิ่มอันนี้เป็นตัวอย่างของการใช้IsDefined

Assembly assembly = ...
var typesWithHelpAttribute = 
        from type in assembly.GetTypes()
        where type.IsDefined(typeof(HelpAttribute), false)
        select type;

3
ฉันเชื่อว่ามันเป็นทางออกที่เหมาะสมที่ใช้วิธีการที่ตั้งใจไว้กรอบ
Alexey Omelchenko

11

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

นี่เป็นตัวอย่างรหัสของฉันที่ทำงานผ่านทุกประเภทในชุดประกอบที่โหลดทั้งหมด:

// this is making the assumption that all assemblies we need are already loaded.
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) 
{
    foreach (Type type in assembly.GetTypes())
    {
        var attribs = type.GetCustomAttributes(typeof(MyCustomAttribute), false);
        if (attribs != null && attribs.Length > 0)
        {
            // add to a cache.
        }
    }
}

9

นี่คือการปรับปรุงประสิทธิภาพที่เหนือกว่าโซลูชันที่ได้รับการยอมรับ การทำซ้ำแม้ว่าคลาสทั้งหมดจะช้าเพราะมีจำนวนมาก บางครั้งคุณสามารถกรองแอสเซมบลีทั้งหมดโดยไม่ดูประเภทใด ๆ

ตัวอย่างเช่นหากคุณกำลังมองหาแอททริบิวที่คุณประกาศด้วยตัวคุณเองคุณไม่คาดหวังว่า DLLs ของระบบจะมีประเภทใด ๆ ที่มีแอททริบิวต์นั้นอยู่ กระบวนการ Assembly.GlobalAssemblyCache คุณสมบัติเป็นวิธีที่รวดเร็วในการตรวจสอบ DLLs ระบบ เมื่อฉันลองสิ่งนี้ในโปรแกรมจริงฉันพบว่าฉันสามารถข้ามประเภท 30,101 และฉันต้องตรวจสอบ 1,983 ประเภทเท่านั้น

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

ฉันรวมทั้งสองอย่างเข้าด้วยกันและทำให้มันเร็วยิ่งขึ้น รหัสด้านล่างมีตัวกรองทั้งสอง

        string definedIn = typeof(XmlDecoderAttribute).Assembly.GetName().Name;
        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            // Note that we have to call GetName().Name.  Just GetName() will not work.  The following
            // if statement never ran when I tried to compare the results of GetName().
            if ((!assembly.GlobalAssemblyCache) && ((assembly.GetName().Name == definedIn) || assembly.GetReferencedAssemblies().Any(a => a.Name == definedIn)))
                foreach (Type type in assembly.GetTypes())
                    if (type.GetCustomAttributes(typeof(XmlDecoderAttribute), true).Length > 0)

4

ในกรณีที่มีข้อ จำกัด Portable .NETรหัสต่อไปนี้ควรใช้งานได้:

    public static IEnumerable<TypeInfo> GetAtributedTypes( Assembly[] assemblies, 
                                                           Type attributeType )
    {
        var typesAttributed =
            from assembly in assemblies
            from type in assembly.DefinedTypes
            where type.IsDefined(attributeType, false)
            select type;
        return typesAttributed;
    }

หรือสำหรับแอสเซมบลีจำนวนมากที่ใช้การวนรอบสถานะyield return:

    public static IEnumerable<TypeInfo> GetAtributedTypes( Assembly[] assemblies, 
                                                           Type attributeType )
    {
        foreach (var assembly in assemblies)
        {
            foreach (var typeInfo in assembly.DefinedTypes)
            {
                if (typeInfo.IsDefined(attributeType, false))
                {
                    yield return typeInfo;
                }
            }
        }
    }

0

เราสามารถปรับปรุงคำตอบของแอนดรูว์และแปลงข้อมูลทั้งหมดให้เป็นหนึ่งคำสั่ง LINQ

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.