วิธีป้องกัน ReflectionTypeLoadException เมื่อเรียก Assembly.GetTypes ()


99

ฉันกำลังพยายามสแกนแอสเซมบลีสำหรับประเภทที่ใช้อินเทอร์เฟซเฉพาะโดยใช้โค้ดที่คล้ายกับสิ่งนี้:

public List<Type> FindTypesImplementing<T>(string assemblyPath)
{
    var matchingTypes = new List<Type>();
    var asm = Assembly.LoadFrom(assemblyPath);
    foreach (var t in asm.GetTypes())
    {
        if (typeof(T).IsAssignableFrom(t))
            matchingTypes.Add(t);
    }
    return matchingTypes;
}

ปัญหาของฉันคือฉันได้รับReflectionTypeLoadExceptionเมื่อโทรasm.GetTypes()ในบางกรณีเช่นหากแอสเซมบลีมีประเภทที่อ้างถึงแอสเซมบลีซึ่งไม่สามารถใช้งานได้ในขณะนี้

ในกรณีของฉันฉันไม่สนใจประเภทที่ทำให้เกิดปัญหา ประเภทที่ฉันกำลังค้นหาไม่จำเป็นต้องมีชุดประกอบที่ไม่มีอยู่

คำถามคือเป็นไปได้ไหมที่จะข้าม / ละเว้นประเภทที่ทำให้เกิดข้อยกเว้น แต่ยังประมวลผลประเภทอื่น ๆ ที่มีอยู่ในชุดประกอบ


1
อาจเป็นการเขียนซ้ำมากกว่าสิ่งที่คุณกำลังมองหา แต่ MEF ให้ฟังก์ชันการทำงานที่คล้ายกัน เพียงทำเครื่องหมายแต่ละชั้นเรียนของคุณด้วยแท็ก [ส่งออก] ที่ระบุอินเทอร์เฟซที่ใช้ จากนั้นคุณสามารถนำเข้าเฉพาะอินเทอร์เฟซที่คุณสนใจในเวลานั้น
Dirk Dastardly

@Drew ขอบคุณสำหรับความคิดเห็นของคุณ ฉันกำลังคิดเกี่ยวกับการใช้ MEF แต่ต้องการดูว่ามีวิธีอื่นที่ถูกกว่าหรือไม่
M4N

การตั้งชื่อโรงงานคลาสปลั๊กอินให้เป็นที่รู้จักเพื่อให้คุณสามารถใช้ Activator.CreateInstance () โดยตรงเป็นวิธีแก้ปัญหาง่ายๆ อย่างไรก็ตามหากคุณได้รับข้อยกเว้นนี้ในขณะนี้เนื่องจากปัญหาการแก้ปัญหาการชุมนุมคุณอาจได้รับในภายหลังเช่นกัน
Hans Passant

1
@ ฮันส์: ฉันไม่แน่ใจว่าฉันเข้าใจทั้งหมด ชุดประกอบที่ฉันกำลังสแกนอาจมีหลายประเภทที่ใช้อินเทอร์เฟซที่กำหนดดังนั้นจึงไม่มีประเภทที่รู้จักกันดี (และ: ฉันกำลังสแกนมากกว่าหนึ่งชุดไม่ใช่แค่ชุดเดียว)
M4N

2
ฉันมีรหัสเกือบเหมือนกันและมีปัญหาเดียวกัน และชุดประกอบที่ฉันสำรวจนั้นใช้AppDomain.CurrentDomain.GetAssemblies()งานได้กับเครื่องของฉัน แต่ไม่ใช่ในเครื่องอื่น ทำไมแอสเซมบลีบางส่วนจากไฟล์ปฏิบัติการของฉันไม่สามารถอ่าน / โหลดได้ ??
v.oddou

คำตอบ:


130

วิธีหนึ่งที่ค่อนข้างน่ารังเกียจคือ:

Type[] types;
try
{
    types = asm.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
    types = e.Types;
}
foreach (var t in types.Where(t => t != null))
{
    ...
}

มันน่ารำคาญมากที่ต้องทำเช่นนี้ คุณสามารถใช้วิธีการขยายเพื่อทำให้ดีขึ้นในโค้ด "ไคลเอนต์":

public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly)
{
    // TODO: Argument validation
    try
    {
        return assembly.GetTypes();
    }
    catch (ReflectionTypeLoadException e)
    {
        return e.Types.Where(t => t != null);
    }
}

คุณอาจต้องการย้ายreturnคำสั่งออกจากบล็อกจับ - ฉันไม่ค่อยกระตือรือร้นที่จะอยู่ที่นั่นด้วยตัวเอง แต่อาจเป็นรหัสที่สั้นที่สุด ...


2
ขอบคุณดูเหมือนว่าจะเป็นวิธีแก้ปัญหา (และฉันเห็นด้วยดูเหมือนจะไม่ใช่วิธีแก้ปัญหาที่สะอาด)
M4N

4
โซลูชันนี้ยังคงมีปัญหาเมื่อคุณพยายามใช้รายการประเภทที่เปิดเผยในข้อยกเว้น ไม่ว่าเหตุผลสำหรับข้อยกเว้นการโหลดประเภท FileNotFound, BadImage ฯลฯ จะยังคงส่งผลต่อการเข้าถึงทุกประเภทที่เป็นปัญหา
sweetfa

@sweetfa: ใช่มัน จำกัด มาก - แต่ถ้า OP แค่ต้องหาชื่อก็น่าจะโอเค
Jon Skeet

1
ขำ ๆ โพสต์นี้อ้างถึงที่นี่น่าสนใจทีเดียว: haacked.com/archive/2012/07/23/…
anhoppe

@sweetfa นี่คือสิ่งที่ฉันทำเพื่อหลีกเลี่ยงปัญหาข้อยกเว้นFileNotFoundในประเภทที่ส่งคืน: From t As Type In e.Types Where (t IsNot Nothing) AndAlso (t.TypeInitializer IsNot Nothing)ดูเหมือนว่าจะใช้งานได้ดี
ElektroStudios

22

ในขณะที่ดูเหมือนว่าไม่มีอะไรสามารถทำได้หากไม่ได้รับ ReflectionTypeLoadException ในบางประเด็นคำตอบข้างต้นมีข้อ จำกัด ว่าการพยายามใช้ประเภทที่ให้มาจากข้อยกเว้นจะยังคงให้ปัญหากับปัญหาเดิมที่ทำให้ชนิดไม่สามารถโหลดได้

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

    /// <summary>
    /// Get the types within the assembly that match the predicate.
    /// <para>for example, to get all types within a namespace</para>
    /// <para>    typeof(SomeClassInAssemblyYouWant).Assembly.GetMatchingTypesInAssembly(item => "MyNamespace".Equals(item.Namespace))</para>
    /// </summary>
    /// <param name="assembly">The assembly to search</param>
    /// <param name="predicate">The predicate query to match against</param>
    /// <returns>The collection of types within the assembly that match the predicate</returns>
    public static ICollection<Type> GetMatchingTypesInAssembly(this Assembly assembly, Predicate<Type> predicate)
    {
        ICollection<Type> types = new List<Type>();
        try
        {
            types = assembly.GetTypes().Where(i => i != null && predicate(i) && i.Assembly == assembly).ToList();
        }
        catch (ReflectionTypeLoadException ex)
        {
            foreach (Type theType in ex.Types)
            {
                try
                {
                    if (theType != null && predicate(theType) && theType.Assembly == assembly)
                        types.Add(theType);
                }
                // This exception list is not exhaustive, modify to suit any reasons
                // you find for failure to parse a single assembly
                catch (BadImageFormatException)
                {
                    // Type not in this assembly - reference to elsewhere ignored
                }
            }
        }
        return types;
    }

4

คุณได้พิจารณาAssembly.ReflectionOnlyLoadหรือไม่? เมื่อพิจารณาถึงสิ่งที่คุณพยายามทำมันอาจเพียงพอแล้ว


2
ใช่ฉันได้พิจารณาแล้ว แต่ฉันไม่ได้ใช้มันเพราะไม่อย่างนั้นฉันจะต้องโหลดการอ้างอิงใด ๆ ด้วยตนเอง นอกจากนี้โค้ดจะไม่สามารถเรียกใช้งานได้ด้วย ReflectionOnlyLoad (ดูส่วนข้อสังเกตในหน้าที่คุณเชื่อมโยง)
M4N

3

ในกรณีของฉันปัญหาเดียวกันนี้เกิดจากการมีแอสเซมบลีที่ไม่ต้องการอยู่ในโฟลเดอร์แอปพลิเคชัน พยายามล้างโฟลเดอร์ Bin และสร้างแอปพลิเคชันใหม่

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