Полное руководство по языку программирования С# 14 и платформе .NET 10 / Процессы и домены приложения

AssemblyLoadContext и динамическая загрузка и выгрузка сборок

Начальный ⏱ 4 мин чтения 👁 2 🕓 обновлено 11.08.2026

В статье Динамическая загрузка сборок и позднее связывание рассматривалось, динамически загружать в приложение сборки и использовать их функционал. Но фреймворк .NET также позволяет выгружать сборки, что позволяет уменьшить объем потребляемой памяти. Для этого применяется класс AssemblyLoadContext из пространства имен System.Runtime.Loader, который представляет контекст загрузки и выгрузки сборок. Рассмотрим, как его использовать.

Допустим, у нас есть консольный проект MyApp со следующим файлом Program.cs:


namespace MyApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var number = 5;
            var result = Square(number);
            Console.WriteLine($"Квадрат {number} равен {result}");
        }
        static int Square(int n) => n * n;
    }
}

Эта программа содержит метод Square для вычисления квадрата, и по умолчанию она компилируется в сборку MyApp.dll. Загрузим эту сборку, чтобы использовать ее метод Square.

Для создания объекта AssemblyLoadContext применяется следующий конструктор:

public AssemblyLoadContext (string? name, bool isCollectible = false);

В конструкторе первый параметр устанавливает имя контекста - это может произвольная строка. Второй параметр - isCollectible устанавливает, можно ли загруженные сборки выгружать. Значение true указывает, что загруженные сборки можно выгружать.

Для загрузки сборок класс AssemblyLoadContext предоставляет ряд методов. Некоторые из них:

  • Assembly LoadFromAssemblyName (AssemblyName assemblyName): загружает определенную сборку по имени, которое представлено типом System.Reflection.AssemblyName

  • Assembly LoadFromAssemblyPath (string assemblyPath): загружает сборку по определенному пути (путь должен быть абсолютным)

  • Assembly LoadFromStream (System.IO.Stream stream): загружает определенную сборку из потока Stream

Использовав один из этих методов, мы можем получить доступ к сборке через тип Assembly и обращаться к ее функционалу.

После завершения работы со сборкой мы можем вызвать у AssemblyLoadContext метод Unload() и выгрузить контекст со всеми загруженными сборками и тем самым снизить потребление памяти и увеличить общую производительность.

Рассмотрим полный пример:


using System.Reflection;
using System.Runtime.Loader;

Square(8);
// очистка памяти
GC.Collect();
GC.WaitForPendingFinalizers();

Console.WriteLine();
// смотрим, какие сборки после выгрузки
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
    Console.WriteLine(asm.GetName().Name);


void Square(int number)
{
    var context = new AssemblyLoadContext(name: "Square", isCollectible: true);
    // установка обработчика выгрузки
    context.Unloading += Context_Unloading;

    // получаем путь к сборке MyApp
    var assemblyPath = Path.Combine(Directory.GetCurrentDirectory(), "MyApp.dll");
    // загружаем сборку
    Assembly assembly = context.LoadFromAssemblyPath(assemblyPath);

    // получаем тип Program из сборки MyApp.dll
    var type = assembly.GetType("MyApp.Program");
    if (type != null)
    {
        // получаем его метод Square
        var squareMethod = type.GetMethod("Square", BindingFlags.Static | BindingFlags.NonPublic);
        // вызываем метод
        var result = squareMethod?.Invoke(null, new object[] { number });
        if (result is int)
        {
            // выводим результат метода на консоль
            Console.WriteLine($"Квадрат числа {number} равен {result}");
        }
    }

    // смотим, какие сборки у нас загружены
    foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
        Console.WriteLine(asm.GetName().Name);

    // выгружаем контекст
    context.Unload();
}

// обработчик выгрузки контекста
void Context_Unloading(AssemblyLoadContext obj)
{
    Console.WriteLine("Библиотека MyApp выгружена");
}

Все эти действия оформляются в виде отдельного метода Square(). В качестве параметра он принимает число, квадрат которого надо вычислить.

Вначале в методе создается объект AssemblyLoadContext:

var context = new AssemblyLoadContext(name: "Square", isCollectible: true);

Обратите внимание, что параметру isCollectible передается значение true, что позволит выгружать ранее загруженные сборки.

Класс AssemblyLoadContext определяет событие Unloadig, благодаря чему мы можем повесить обработчик и определить момент выгрузки контекста.

context.Unloading += Context_Unloading;

Далее используется метод LoadFromAssemblyPath для загузки сборки MyApp.dll по абсолютному пути. В данном случае предполагается, что файл сборки находится в одной папке с текущим приложением.

Assembly assembly = context.LoadFromAssemblyPath(assemblyPath);

Получив сборку, с помощью рефлексии обращаемся к методу Square и получаем квадрат числа.

Затем смотрим, какие сборки загружены в текущий домен. Среди них мы сможем найти и MyApp.dll. И в конце выгружаем контекст:

context.Unload();

Данный метод Square вызывается в методе Main:


Square(8);
// очистка
GC.Collect();
GC.WaitForPendingFinalizers();

Но обратите внимание, что выгрузка контекста сама по себе не означает немедленной очистки памяти. Вызов метода Unload только инициирует процесс выгрузки, реальная выгрузка произойдет лишь тогда, когда в дело вступит автоматический сборщик мусора и удалит соответствующие объекты. Поэтому для более быстрой очистки в конце вызываются методы GC.Collect() и GC.WaitForPendingFinalizers().

Консольный вывод:


Квадрат числа 8 равен 64

System.Private.CoreLib
HelloApp
System.Runtime
Microsoft.Extensions.DotNetDeltaApplier
System.IO.Pipes
System.Linq
System.Collections
System.Console
System.Runtime.Loader
MyApp
System.Collections.Concurrent
System.Threading
System.Text.Encoding.Extensions
Библиотека MyApp выгружена

System.Private.CoreLib
HelloApp
System.Runtime
Microsoft.Extensions.DotNetDeltaApplier
System.IO.Pipes
System.Linq
System.Collections
System.Console
System.Runtime.Loader
System.Threading.Overlapped
System.Collections.Concurrent
System.Threading
System.Text.Encoding.Extensions

Как видно, после выгрузки контекста AssemblyLoadContext сборки MyApp в списке загруженных сборок больше не наблюдается.

Проверь себя

📋 Тест по теме «AssemblyLoadContext и динамическая загрузка и выгрузка сборок» Тест по теме

Практические задания

Лёгкое Ручная проверка Заполните пропуск в коде (1)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

namespace _____
{
    class Program
    {
        static void Main(string[] args)
        {
            var number = 5;
            var result = Square(number);
            Console.WriteLine($"Квадрат {number} равен {result}");
        }
        static int Square(int n) => n * n;
    }
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (2)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

public AssemblyLoadContext (string? _____, bool isCollectible = false);
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (3)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

using _____.Reflection;
using System.Runtime.Loader;

Square(8);
// очистка памяти
GC.Collect();
GC.WaitForPendingFinalizers();

Console.WriteLine();
// смотрим, какие сборки после выгрузки
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
    Console.WriteLine(asm.GetName().Name);


void Square(int number)
{
    var context = new AssemblyLoadContext(name: "Square", isCollectible: true);
    // установка обработчика выгрузки
    context.Unloading += Context_Unloading;

    // получаем путь к сборке MyApp
    var assemblyPath = Path.Combine(Directory.GetCurrentDirectory(), "MyApp.dll");
    // загружаем сборку
    Assembly assembly = context.LoadFromAssemblyPath(assemblyPath);

    // получаем тип Program из сборки MyApp.dll
    var type = assembly.GetType("MyApp.Program");
    if (type != null)
    {
        // получаем его метод Square
        var squareMethod = type.GetMethod("Square", BindingFlags.Static | BindingFlags.NonPublic);
        // вызываем метод
        var result = squareMethod?.Invoke(null, new object[] { number });
        if (result is int)
        {
            // выводим результат метода на консоль
            Console.WriteLine($"Квадрат числа {number} равен {result}");
        }
    }

    // смотим, какие сборки у нас загружены
    foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
        Console.WriteLine(asm.GetName().Name);

    // выгружаем контекст
    context.Unload();
}

// обработчик выгрузки контекста
void Context_Unloading(AssemblyLoadContext obj)
{
    Console.WriteLine("Библиотека MyApp выгружена");
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (4)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

var context = new AssemblyLoadContext(_____: "Square", isCollectible: true);
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (5)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

context._____ += Context_Unloading;
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (1)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

namespace MyApp
{
    class Program
    {
        static void Main(string[] _____)
        {
            var number = 5;
            var result = Square(number);
            Console._____($"Квадрат {number} равен {result}");
        }
        static int Square(int n) => n * n;
    }
}
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (2)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

public AssemblyLoadContext (string? name, _____ _____ = false);
Войдите, чтобы отправить решение
Среднее Ручная проверка Расставьте строки кода в правильном порядке
+20 XP

Строки кода из примера этого урока перемешаны. Впишите их в правильном порядке, каждую строку — с новой строки.

// очистка
GC.WaitForPendingFinalizers();
GC.Collect();
Square(8);
Войдите, чтобы отправить решение
Сложное Ручная проверка Объясните и модифицируйте пример из урока «AssemblyLoadContext и динамическая загрузка и выгрузка сборок»
+30 XP

Опишите своими словами, что делает приведённый пример кода из урока «AssemblyLoadContext и динамическая загрузка и выгрузка сборок», и предложите одно осмысленное изменение (например, другое входное значение или дополнительное условие). Приведите итоговый код.

namespace MyApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var number = 5;
            var result = Square(number);
            Console.WriteLine($"Квадрат {number} равен {result}");
        }
        static int Square(int n) => n * n;
    }
}
Войдите, чтобы отправить решение
Сложное Ручная проверка Найдите потенциальную проблему в коде урока «AssemblyLoadContext и динамическая загрузка и выгрузка сборо»
+30 XP

Изучите приведённый фрагмент кода и опишите, при каких входных данных или условиях он может сработать не так, как ожидается (граничный случай, отсутствие проверки и т.п.). Предложите исправленный вариант.

public AssemblyLoadContext (string? name, bool isCollectible = false);
Войдите, чтобы отправить решение
Мини-проект Ручная проверка Мини-проект по теме «AssemblyLoadContext и динамическая загрузка и выгрузка сборок»
+50 XP

Соберите небольшую программу, которая последовательно демонстрирует ключевые идеи урока. Программа должна быть рабочей и содержать минимум два самостоятельно написанных фрагмента, а не просто копию примера из урока.

Войдите, чтобы отправить решение
Домашнее задание Ручная проверка Домашнее задание по теме «AssemblyLoadContext и динамическая загрузка и выгрузка сборок»
+25 XP

Напишите собственную небольшую программу, применяющую материал темы «AssemblyLoadContext и динамическая загрузка и выгрузка сборок». Используйте не менее одной конструкции, показанной в примерах урока. Прикрепите код решения и краткое описание того, что он делает.

Войдите, чтобы отправить решение

Обсуждение (0)

Войдите, чтобы оставить комментарий.