Если создать домен и загрузить в него сборку, а потом используя метод GetAssemblies, достать сборки, то выгружая домен, сборка остается в основном домене приложения и с ней ничего сделать нельзя, удалить например.
Каким образом можно использовать методы сборки из чужого домена, при этом выгружая домен выгрузить и сборку из приложения?
Вот код которым я это делаю:
using System;
using System.IO;
using System.Reflection;
namespace Test
{
internal static class Program
{
private static void Main()
{
var appDomain = AppDomain.CreateDomain( "test" );
AssemblyLoader.Run(appDomain);
Console.WriteLine("Сборка загружена");
foreach ( var assembly in appDomain.GetAssemblies () )
{
Console.WriteLine ( assembly.GetName () );
}
Console.ReadLine();
AppDomain.Unload( appDomain );
Console.WriteLine("Домен выгружен");
//Не удается удалить сборку из каталога, она не выгрузилась!
Console.ReadLine();
}
}
public static class AssemblyLoader
{
public static void Run( AppDomain appDomain )
{
appDomain.DoCallBack( Invoke );
}
private static void Invoke()
{
Assembly.LoadFile( Path.Combine( Environment.CurrentDirectory, "test.dll" ) );
}
}
}
Ответ
Если вы передаёте объект из другого домена, вы этим самым загружаете его в ваш домен, чего вы как раз хотите избежать. Решением может быть передавать примитивные типы. Чтобы не ограничивать себя простыми колбеками, вам нужно воспользоваться MarshalByRefObject (они не копируются между доменами, а выполняются в том домене, где созданы).
Итак, вот рабочий код.
Это плагин, DomainsPlugin.dll:
namespace DomainsPlugin
{
public class Plugin
{
public IEnumerable GetNames()
{
return AppDomain.CurrentDomain.GetAssemblies().Select(asm => asm.GetName());
}
}
}
Ничего особого, просто рабочий метод.
Теперь главная программа:
namespace DomainsMain
{
class Program
{
static void Main(string[] args)
{
// в этом каталоге бежит программа
var executableDir = Path.GetDirectoryName(
Assembly.GetExecutingAssembly().Location);
// это каталог, где лежит плагин.
// у вас он будет лежать, понятно, в другом месте
var pluginDir = Path.GetFullPath(Path.Combine(
executableDir, "..", "..", "..", "DomainsPlugin", "bin", "Debug"));
// оригинал плагина
var pluginSourceFile = Path.Combine(pluginDir, "DomainsPlugin.dll");
// а сюда мы его скопируем, и отсюда будем загружать
var pluginWorkingFile = Path.Combine(executableDir, "DomainsPlugin.dll");
// копируем
File.Copy(pluginSourceFile, pluginWorkingFile, overwrite: true);
// окей, теперь новый домен
var pluginDomain = AppDomain.CreateDomain("Plugin");
// теперь, создаём экземпляр нашего класса в новом домене
// у нас на него реально лишь прокси-объект
var resident = (Resident)pluginDomain.CreateInstanceAndUnwrap(
Assembly.GetExecutingAssembly().FullName,
"DomainsMain.Resident");
// получили данные
resident.Obtain();
// выводим их
foreach (var r in resident.Result)
Console.WriteLine(r.ToString());
// выгружаем домен...
AppDomain.Unload(pluginDomain);
// ... и удаляем плагин
File.Delete(pluginWorkingFile);
}
}
// простой класс, будет выполняться в другом домене
public class Resident : MarshalByRefObject
{
public List Result;
public void Obtain()
{
// загружаем библиотеку
var asm = Assembly.LoadFrom("DomainsPlugin.dll");
var type = asm.GetType("DomainsPlugin.Plugin");
// и вызываем функцию из неё через рефлексию
// можно было бы закастить к интерфейсу, если объявить его в
// этой или другой общей dll-ке
var p = Activator.CreateInstance(type);
var method = type.GetMethod(
"GetNames", BindingFlags.Instance | BindingFlags.Public);
var result = (IEnumerable)method.Invoke(p, null);
// сохраняем результат. это поле будет доступно из первоначального домена
Result = result.ToList();
}
}
}
На моей машине из-под Visual Studio выдаёт:
mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Microsoft.VisualStudio.HostingProcess.Utilities, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
DomainsMain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
DomainsPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Обновление: резидентная функция (Obtain()) вполне может возвращать значение. Но это значение должно быть сериализируемым, чтобы «протиснуться» между доменами.
Для нашего примера, подправляем класс Resident
public class Resident : MarshalByRefObject
{
public List Obtain() // <-- поменяли возвращаемый тип
{ // и убрали свойство Result
// загружаем библиотеку
var asm = Assembly.LoadFrom("DomainsPlugin.dll");
var type = asm.GetType("DomainsPlugin.Plugin");
// и вызываем функцию из неё через рефлексию
// можно было бы закастить к интерфейсу, если объявить его в
// этой или другой общей dll-ке
var p = Activator.CreateInstance(type);
var method = type.GetMethod(
"GetNames", BindingFlags.Instance | BindingFlags.Public);
var result = (IEnumerable)method.Invoke(p, null);
return result.ToList();
}
}
Теперь вызов из основного кода выглядит так:
// получили данные
var result = resident.Obtain();
// выводим их
foreach (var r in result)
Console.WriteLine(r.ToString());