Bagikan melalui


Mengatasi beban assembly

.NET menyediakan event AppDomain.AssemblyResolve untuk aplikasi yang memerlukan kontrol yang lebih besar atas pemanggilan assembly. Dengan menangani peristiwa ini, aplikasi Anda dapat memuat rakitan ke dalam konteks beban dari luar jalur pemeriksaan normal, memilih versi rakitan mana yang akan dimuat, memancarkan rakitan dinamis dan mengembalikannya, dan sebagainya. Topik ini memberikan panduan untuk menangani kejadian AssemblyResolve.

Nota

Untuk menyelesaikan muatan assembly dalam konteks hanya-refleksi, gunakan event AppDomain.ReflectionOnlyAssemblyResolve sebagai gantinya.

Cara kerja event AssemblyResolve

Saat Anda mendaftarkan handler untuk AssemblyResolve event, handler akan dipanggil setiap kali runtime gagal menghubungkan ke assembly berdasarkan nama. Misalnya, memanggil metode berikut dalam kode pengguna dapat menyebabkan acara AssemblyResolve dinaikkan:

Apa yang dilakukan pengendali acara

Penangan untuk kejadian AssemblyResolve menerima nama tampilan dari rakitan yang akan dimuat, di properti ResolveEventArgs.Name. Jika handler tidak mengenali nama rakitan, handler akan mengembalikan null (C#), Nothing (Visual Basic), atau nullptr (Visual C++).

Jika handler mengenali nama assembly, handler dapat memuat dan mengembalikan assembly yang memenuhi permintaan. Daftar berikut ini menjelaskan beberapa skenario sampel.

  • Jika handler mengetahui lokasi versi assembly, handler dapat memuat assembly dengan menggunakan metode Assembly.LoadFrom atau Assembly.LoadFile, dan dapat mengembalikan assembly yang dimuat jika berhasil.

  • Jika handler memiliki akses ke database assembly yang disimpan sebagai array byte, handler dapat memuat array byte dengan menggunakan salah satu metode overload Assembly.Load yang menerima array byte.

  • Handler dapat menghasilkan rakitan dinamis dan mengembalikannya.

Nota

Handler harus memuat rakitan ke dalam konteks load-from, ke dalam konteks beban, atau tanpa konteks. Jika handler memuat assembly ke dalam konteks khusus refleksi dengan menggunakan metode Assembly.ReflectionOnlyLoad atau Assembly.ReflectionOnlyLoadFrom, upaya pemuatan yang menaikkan event AssemblyResolve gagal.

Merupakan tanggung jawab penangan acara untuk mengembalikan assembli yang sesuai. Handler dapat mengurai nama tampilan rakitan yang diminta dengan meneruskan ResolveEventArgs.Name nilai properti ke AssemblyName(String) konstruktor. Dimulai dengan .NET Framework 4, handler dapat menggunakan elemen ResolveEventArgs.RequestingAssembly untuk menentukan apakah permintaan saat ini adalah dependensi dari assembly lain. Informasi ini dapat membantu mengidentifikasi komponen yang akan memenuhi ketergantungan.

Pengendali acara dapat mengembalikan versi perakitan yang berbeda dari versi yang diminta.

Dalam sebagian besar kasus, rakitan yang dikembalikan oleh pemuat muncul dalam konteks muatan, tidak peduli konteks di mana pemuat tersebut memuat rakitan tersebut. Misalnya, jika handler menggunakan Assembly.LoadFrom metode untuk memuat assembly ke dalam konteks load-from, assembly muncul dalam konteks beban saat handler mengembalikannya. Namun, dalam kasus berikut rakitan muncul tanpa konteks ketika handler mengembalikannya:

Untuk informasi tentang konteks, lihat Assembly.LoadFrom(String) metode kelebihan beban.

Beberapa versi rakitan yang sama dapat dimuat ke domain aplikasi yang sama. Praktik ini tidak disarankan, karena dapat menyebabkan masalah penetapan jenis. Lihat Praktik terbaik untuk pemuatan perakitan.

Apa yang tidak boleh dilakukan oleh penanganan aktivitas

Aturan utama untuk menangani peristiwa AssemblyResolve adalah Anda tidak boleh mencoba mengembalikan assembly yang tidak Anda kenali. Ketika Anda menulis handler, Anda harus tahu assembly mana yang dapat memicu event. Handler Anda harus mengembalikan null untuk rakitan lain.

Penting

Dimulai dengan .NET Framework 4, event AssemblyResolve diaktifkan untuk assembly satelit. Perubahan ini memengaruhi penanganan aktivitas yang ditulis untuk versi .NET Framework yang lebih lama, jika handler mencoba menyelesaikan semua permintaan pemuatan rakitan. Penangan kejadian yang mengabaikan assembly yang tidak dikenali tidak terpengaruh oleh perubahan ini: Mereka mengembalikan null, dan mekanisme fallback normal diikuti.

Saat memuat rakitan, penangan kejadian tidak boleh menggunakan fungsi overloading AppDomain.Load atau Assembly.Load yang dapat menyebabkan AssemblyResolve kejadian dipicu secara rekursif, karena ini dapat menyebabkan kelebihan tumpukan. (Lihat daftar yang disediakan sebelumnya dalam topik ini.) Ini terjadi bahkan jika Anda memberikan penanganan pengecualian untuk permintaan pemuatan, karena tidak ada pengecualian yang dilemparkan sampai semua penangan peristiwa telah kembali. Dengan demikian, kode berikut menghasilkan luapan tumpukan jika MyAssembly tidak ditemukan:

using System;
using System.Reflection;

class BadExample
{
    static void Main()
    {
        AppDomain ad = AppDomain.CreateDomain("Test");
        ad.AssemblyResolve += MyHandler;

        try
        {
            object obj = ad.CreateInstanceAndUnwrap(
                "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
                "MyType");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    static Assembly MyHandler(object source, ResolveEventArgs e)
    {
        Console.WriteLine("Resolving {0}", e.Name);
        // DO NOT DO THIS: This causes a StackOverflowException
        return Assembly.Load(e.Name);
    }
}

/* This example produces output similar to the following:

Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
...
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null

Process is terminated due to StackOverflowException.
 */
Imports System.Reflection

Class BadExample

    Shared Sub Main()

        Dim ad As AppDomain = AppDomain.CreateDomain("Test")
        AddHandler ad.AssemblyResolve, AddressOf MyHandler

        Try
            Dim obj As object = ad.CreateInstanceAndUnwrap(
                "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
                "MyType")
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try
    End Sub

    Shared Function MyHandler(ByVal source As Object, _
                              ByVal e As ResolveEventArgs) As Assembly
        Console.WriteLine("Resolving {0}", e.Name)
        // DO NOT DO THIS: This causes a StackOverflowException
        Return Assembly.Load(e.Name)
    End Function
End Class

' This example produces output similar to the following:
'
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'...
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'
'Process is terminated due to StackOverflowException.
using namespace System;
using namespace System::Reflection;

ref class Example
{
internal:
    static Assembly^ MyHandler(Object^ source, ResolveEventArgs^ e)
    {
        Console::WriteLine("Resolving {0}", e->Name);
        // DO NOT DO THIS: This causes a StackOverflowException
        return Assembly::Load(e->Name);
    }
};

void main()
{
    AppDomain^ ad = AppDomain::CreateDomain("Test");
    ad->AssemblyResolve += gcnew ResolveEventHandler(&Example::MyHandler);

    try
    {
        Object^ obj = ad->CreateInstanceAndUnwrap(
            "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
            "MyType");
    }
    catch (Exception^ ex)
    {
        Console::WriteLine(ex->Message);
    }
}

/* This example produces output similar to the following:

Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
...
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null

Process is terminated due to StackOverflowException.
*/

Cara yang benar untuk menangani AssemblyResolve

Saat menyelesaikan rakitan dari penangan kejadian AssemblyResolve, sebuah StackOverflowException pada akhirnya akan dilemparkan jika penangan menggunakan panggilan metode Assembly.Load atau AppDomain.Load. Sebagai gantinya, gunakan metode LoadFile atau LoadFrom, karena tidak memicu event AssemblyResolve.

Bayangkan bahwa MyAssembly.dll terletak di dekat assembly yang dieksekusi, di lokasi yang diketahui, dan dapat diselesaikan menggunakan Assembly.LoadFile dengan memberikan jalur ke assembly tersebut.

using System;
using System.IO;
using System.Reflection;

class CorrectExample
{
    static void Main()
    {
        AppDomain ad = AppDomain.CreateDomain("Test");
        ad.AssemblyResolve += MyHandler;

        try
        {
            object obj = ad.CreateInstanceAndUnwrap(
                "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
                "MyType");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    static Assembly MyHandler(object source, ResolveEventArgs e)
    {
        Console.WriteLine("Resolving {0}", e.Name);

        var path = Path.GetFullPath("../../MyAssembly.dll");
        return Assembly.LoadFile(path);
     }
}
Imports System.IO
Imports System.Reflection

Class CorrectExample

    Shared Sub Main()

        Dim ad As AppDomain = AppDomain.CreateDomain("Test")
        AddHandler ad.AssemblyResolve, AddressOf MyHandler

        Try
            Dim obj As Object = ad.CreateInstanceAndUnwrap(
                "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
                "MyType")
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try
    End Sub

    Shared Function MyHandler(ByVal source As Object,
                              ByVal e As ResolveEventArgs) As Assembly
        Console.WriteLine("Resolving {0}", e.Name)

        Dim fullPath = Path.GetFullPath("../../MyAssembly.dll")
        Return Assembly.LoadFile(fullPath)
    End Function
End Class
using namespace System;
using namespace System::IO;
using namespace System::Reflection;

ref class Example
{
internal:
    static Assembly^ MyHandler(Object^ source, ResolveEventArgs^ e)
    {
        Console::WriteLine("Resolving {0}", e->Name);

        String^ fullPath = Path::GetFullPath("../../MyAssembly.dll");
        return Assembly::LoadFile(fullPath);
    }
};

void main()
{
    AppDomain^ ad = AppDomain::CreateDomain("Test");
    ad->AssemblyResolve += gcnew ResolveEventHandler(&Example::MyHandler);

    try
    {
        Object^ obj = ad->CreateInstanceAndUnwrap(
            "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
            "MyType");
    }
    catch (Exception^ ex)
    {
        Console::WriteLine(ex->Message);
    }
}

Lihat juga