Mengatasi beban assembly

.NET menyediakan aktivitas AppDomain.AssemblyResolve untuk aplikasi yang memerlukan kontrol yang lebih besar atas pemuatan assembly. Dengan menangani aktivitas ini, aplikasi Anda dapat memuat assembly ke dalam konteks beban dari luar jalur probing normal, pilih mana dari beberapa versi assembly yang akan dimuat, memancarkan assembly dinamis dan mengembalikannya, dan sebagainya. Topik ini memberikan panduan untuk menangani aktivitas AssemblyResolve.

Catatan

Untuk menyelesaikan beban assembly dalam konteks khusus refleksi, gunakan aktivitas AppDomain.ReflectionOnlyAssemblyResolve sebagai gantinya.

Cara kerja aktivitas AssemblyResolve

Saat Anda mendaftarkan handler untuk aktivitas AssemblyResolve, handler dipanggil setiap kali runtime gagal mengikat ke assembly berdasarkan nama. Misalnya, memanggil metode berikut dari kode pengguna dapat menyebabkan aktivitas AssemblyResolve dinaikkan:

Apa yang dilakukan penanganan aktivitas

Handler untuk aktivitas AssemblyResolve menerima nama tampilan assembly yang akan dimuat, di properti ResolveEventArgs.Name. Jika handler tidak mengenali nama assembly, maka akan mengembalikan null (C#), Nothing (Visual Basic), atau nullptr (Visual C++).

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

  • 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 rakitan yang disimpan sebagai array byte, handler dapat memuat array byte dengan menggunakan salah satu kelebihan beban metode Assembly.Load yang mengambil array byte.

  • Handler dapat menghasilkan assembly dinamis dan mengembalikannya.

Catatan

Handler harus memuat assembly ke dalam konteks beban-dari, ke dalam konteks beban, atau tanpa konteks. Jika handler memuat assembly ke dalam konteks hanya refleksi dengan menggunakan metode Assembly.ReflectionOnlyLoad atau Assembly.ReflectionOnlyLoadFrom, upaya beban yang menaikkan aktivitas AssemblyResolve gagal.

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

Penangan aktivitas dapat mengembalikan versi assembly yang berbeda dari versi yang diminta.

Dalam kebanyakan kasus, assembly yang dikembalikan oleh handler muncul dalam konteks beban, terlepas dari konteks handler memuatnya ke dalam. Misalnya, jika handler menggunakan metode Assembly.LoadFrom untuk memuat assembly ke dalam konteks load-from, assembly muncul dalam konteks beban saat handler mengembalikannya. Namun, dalam kasus berikut assembly muncul tanpa konteks ketika penangan mengembalikannya:

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

Beberapa versi dari assembly yang sama dapat dimuat ke dalam domain aplikasi yang sama. Praktik ini tidak dianjurkan, karena dapat menyebabkan masalah penugasan jenis. Lihat Praktik terbaik untuk pemuatan assembly.

Yang tidak boleh dilakukan oleh penangan aktivitas

Aturan utama untuk menangani aktivitas AssemblyResolve adalah Anda tidak boleh mencoba mengembalikan assembly yang tidak Anda kenali. Ketika Anda menulis handler, Anda harus tahu rakitan mana yang dapat menyebabkan aktivitas tersebut dinaikkan. Handler Anda harus mengembalikan null untuk assembly lainnya.

Penting

Dimulai dengan .NET Framework 4, aktivtas AssemblyResolve dinaikkan untuk assembly satelit. Perubahan ini memengaruhi penangan aktivitas yang ditulis untuk versi .NET Framework sebelumnya, jika penangan mencoba menyelesaikan semua permintaan beban assembly. Penanganan aktivitas yang mengabaikan assembly yang tidak mereka kenali tidak terpengaruh oleh perubahan ini: Mereka mengembalikan null, dan mekanisme fallback normal diikuti.

Saat memuat assembly, penanganan aktivitas tidak boleh menggunakan kelebihan beban metode AppDomain.Load atau Assembly.Load yang dapat menyebabkan aktivtas AssemblyResolve dinaikkan secara rekursif, karena ini dapat menyebabkan luapan tumpukan. (Lihat daftar yang disediakan sebelumnya dalam topik ini.) Hal ini terjadi meskipun Anda memberikan penanganan pengecualian untuk permintaan beban, karena tidak terkecuali dilemparkan sampai semua penangan aktivitas 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 assembly dari penanganan aktivitas AssemblyResolve, StackOverflowException pada akhirnya akan dilemparkan jika handler menggunakan panggilan metode Assembly.Load atau AppDomain.Load. Sebagai gantinya, gunakan metode LoadFile atau LoadFrom, karena tidak meningkatkan aktivitasAssemblyResolve.

Bayangkan MyAssembly.dll terletak di dekat assembly yang dieksekusi, di lokasi yang diketahui, ia dapat diselesaikan menggunakan Assembly.LoadFile mengingat jalur ke assembly.

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