คุณวนลูปแอสเซมบลีที่โหลดในปัจจุบันได้อย่างไร?


120

ฉันมีหน้า "การวินิจฉัย" ในแอปพลิเคชัน ASP.NET ซึ่งทำสิ่งต่างๆเช่นตรวจสอบการเชื่อมต่อฐานข้อมูลแสดง appSettings และ ConnectionStrings ปัจจุบันเป็นต้นส่วนของหน้านี้จะแสดงประเภทที่สำคัญของ Assembly เวอร์ชันที่ใช้ตลอด แต่ฉันไม่สามารถหาวิธีแสดงเวอร์ชันของชุดประกอบทั้งหมดที่โหลดได้อย่างมีประสิทธิภาพ

วิธีใดที่มีประสิทธิภาพที่สุดในการค้นหาแอสเซมบลีที่อ้างถึงและ / หรือโหลดในแอปพลิเคชัน. NET

หมายเหตุ: ฉันไม่สนใจวิธีการใช้ไฟล์เช่นการวนซ้ำผ่าน * .dll ในไดเร็กทอรีเฉพาะ ฉันสนใจว่าตอนนี้แอปพลิเคชันกำลังใช้งานอะไรอยู่

คำตอบ:


24

เมธอดส่วนขยายนี้ได้รับแอสเซมบลีที่อ้างอิงทั้งหมดแบบวนซ้ำรวมถึงแอสเซมบลีที่ซ้อนกัน

ตามที่ใช้ReflectionOnlyLoadงานมันจะโหลดแอสเซมบลีใน AppDomain แยกต่างหากซึ่งมีข้อดีคือไม่รบกวนกระบวนการ JIT

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

/// <summary>
///     Intent: Get referenced assemblies, either recursively or flat. Not thread safe, if running in a multi
///     threaded environment must use locks.
/// </summary>
public static class GetReferencedAssemblies
{
    static void Demo()
    {
        var referencedAssemblies = Assembly.GetEntryAssembly().MyGetReferencedAssembliesRecursive();
        var missingAssemblies = Assembly.GetEntryAssembly().MyGetMissingAssembliesRecursive();
        // Can use this within a class.
        //var referencedAssemblies = this.MyGetReferencedAssembliesRecursive();
    }

    public class MissingAssembly
    {
        public MissingAssembly(string missingAssemblyName, string missingAssemblyNameParent)
        {
            MissingAssemblyName = missingAssemblyName;
            MissingAssemblyNameParent = missingAssemblyNameParent;
        }

        public string MissingAssemblyName { get; set; }
        public string MissingAssemblyNameParent { get; set; }
    }

    private static Dictionary<string, Assembly> _dependentAssemblyList;
    private static List<MissingAssembly> _missingAssemblyList;

    /// <summary>
    ///     Intent: Get assemblies referenced by entry assembly. Not recursive.
    /// </summary>
    public static List<string> MyGetReferencedAssembliesFlat(this Type type)
    {
        var results = type.Assembly.GetReferencedAssemblies();
        return results.Select(o => o.FullName).OrderBy(o => o).ToList();
    }

    /// <summary>
    ///     Intent: Get assemblies currently dependent on entry assembly. Recursive.
    /// </summary>
    public static Dictionary<string, Assembly> MyGetReferencedAssembliesRecursive(this Assembly assembly)
    {
        _dependentAssemblyList = new Dictionary<string, Assembly>();
        _missingAssemblyList = new List<MissingAssembly>();

        InternalGetDependentAssembliesRecursive(assembly);

        // Only include assemblies that we wrote ourselves (ignore ones from GAC).
        var keysToRemove = _dependentAssemblyList.Values.Where(
            o => o.GlobalAssemblyCache == true).ToList();

        foreach (var k in keysToRemove)
        {
            _dependentAssemblyList.Remove(k.FullName.MyToName());
        }

        return _dependentAssemblyList;
    }

    /// <summary>
    ///     Intent: Get missing assemblies.
    /// </summary>
    public static List<MissingAssembly> MyGetMissingAssembliesRecursive(this Assembly assembly)
    {
        _dependentAssemblyList = new Dictionary<string, Assembly>();
        _missingAssemblyList = new List<MissingAssembly>();
        InternalGetDependentAssembliesRecursive(assembly);

        return _missingAssemblyList;
    }

    /// <summary>
    ///     Intent: Internal recursive class to get all dependent assemblies, and all dependent assemblies of
    ///     dependent assemblies, etc.
    /// </summary>
    private static void InternalGetDependentAssembliesRecursive(Assembly assembly)
    {
        // Load assemblies with newest versions first. Omitting the ordering results in false positives on
        // _missingAssemblyList.
        var referencedAssemblies = assembly.GetReferencedAssemblies()
            .OrderByDescending(o => o.Version);

        foreach (var r in referencedAssemblies)
        {
            if (String.IsNullOrEmpty(assembly.FullName))
            {
                continue;
            }

            if (_dependentAssemblyList.ContainsKey(r.FullName.MyToName()) == false)
            {
                try
                {
                    var a = Assembly.ReflectionOnlyLoad(r.FullName);
                    _dependentAssemblyList[a.FullName.MyToName()] = a;
                    InternalGetDependentAssembliesRecursive(a);
                }
                catch (Exception ex)
                {
                    _missingAssemblyList.Add(new MissingAssembly(r.FullName.Split(',')[0], assembly.FullName.MyToName()));
                }
            }
        }
    }

    private static string MyToName(this string fullName)
    {
        return fullName.Split(',')[0];
    }
}

ปรับปรุง

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


ฉันเพิ่งเขียนสิ่งนี้ใหม่เพื่อให้เธรดปลอดภัยดังนั้นจึงสามารถเรียกใช้จากเธรดต่างๆพร้อมกันได้ (ไม่แน่ใจว่าทำไมคุณถึงต้องการสิ่งนั้น แต่เดี๋ยวก่อนมันปลอดภัยกว่า) โปรดแจ้งให้เราทราบหากคุณต้องการให้ฉันโพสต์รหัส
Contango

2
@Contango คุณสามารถโพสต์เวอร์ชันที่ปลอดภัยของเธรดของคุณได้หรือถ้าคุณเขียนบล็อกเกี่ยวกับเรื่องนี้โพสต์นั้น?
Robert

2
วิธีที่ไร้เดียงสาในการทำให้เธรดนี้ปลอดภัยคือการใส่ข้อมูลlockทั้งหมด วิธีอื่นที่ฉันใช้กำจัดการพึ่งพาบนโกลบอลสแตติก "_dependentAssemblyList" ดังนั้นจึงกลายเป็นเธรดที่ปลอดภัยโดยไม่ต้องใช้ a lockซึ่งมีข้อได้เปรียบด้านความเร็วเล็กน้อยหากเธรดหลายเธรดพยายามระบุพร้อมกันว่าแอสเซมบลีใดหายไป (นี่คือบิตของ กรณีมุม)
Contango

3
การเพิ่ม a lockจะไม่เพิ่มมากในลักษณะของ "thread safe" แน่นอนว่านั่นทำให้บล็อกโค้ดนั้นรันทีละรายการเท่านั้น แต่เธรดอื่นสามารถโหลดแอสเซมบลีได้ทุกเมื่อที่ต้องการและอาจทำให้เกิดปัญหากับบางส่วนของforeachลูป
Peter Ritchie

1
@Peter Ritchie มีตัวแปรส่วนกลางแบบคงที่ที่ใช้ร่วมกันซึ่งใช้ในระหว่างการเรียกซ้ำดังนั้นการเพิ่มการล็อกการเข้าถึงทั้งหมดจะทำให้เธรดส่วนนั้นปลอดภัย เป็นเพียงการฝึกเขียนโปรแกรมที่ดี โดยปกติแล้วแอสเซมบลีที่จำเป็นทั้งหมดจะถูกโหลดเมื่อเริ่มต้นใช้งานเว้นแต่จะมีการใช้บางอย่างเช่น MEF ดังนั้นความปลอดภัยของเธรดจึงไม่ใช่ปัญหาในทางปฏิบัติ
Contango

193

การโหลดแอสเซมบลีสำหรับปัจจุบันAppDomain:

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

การรับแอสเซมบลีที่อ้างอิงโดยแอสเซมบลีอื่น:

var referencedAssemblies = someAssembly.GetReferencedAssemblies();

โปรดทราบว่าหากมีการโหลดแอสเซมบลี A อ้างอิงแอสเซมบลี B และแอสเซมบลี A นั่นไม่ได้หมายความว่าแอสเซมบลี B ถูกโหลดด้วย แอสเซมบลี B จะถูกโหลดก็ต่อเมื่อจำเป็นเท่านั้น ด้วยเหตุนี้จึงGetReferencedAssemblies()ส่งคืนAssemblyNameอินสแตนซ์แทนAssemblyอินสแตนซ์


2
ฉันต้องการอะไรแบบนี้ - ด้วยโซลูชัน. net ฉันต้องการค้นหาชุดประกอบที่อ้างอิงทั้งหมดในโครงการทั้งหมด ไอเดีย?
Kumar Vaibhav

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

3
OP ขอให้แอสเซมบลีที่โหลดอยู่ในขณะนี้ไม่ได้อ้างอิง สิ่งนี้ตอบคำถาม สิ่งที่ฉันกำลังมองหา
MikeJansen

เหตุการณ์ที่ทราบเมื่อมีการโหลด Assembly B?
Kiquenet
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.