Перезагрузка классов в C# - странное поведение
Вступление
Я модифицирую игру и хочу динамически перезагружать моды во время выполнения, чтобы мне не приходилось перезапускать игру каждый раз, когда я вносил изменения. Я создал моддинг API, так что мод это просто класс, реализующий мой ICustomCommand
интерфейс:
public interface ICustomCommand
{
string GetAuthor(); //get author, for good measure
string GetFunctionName(); //get name, to differentiate from other mods
void Execute(CrplCore core); //actually do the work
}
Пример:
public class HelloCommandExample : ICustomCommand
{
public void Execute(CrplCore core)
{
DebugText.LogMessage(core, "Hello mods: " + core.GetStringFromStack());
Debug.Log("HelloCommandExample");
}
public string GetAuthor()
{
return "kajacx";
}
public string GetFunctionName()
{
return "HelloTrace";
}
}
Как это работает прямо сейчас: Моддеры могут поместить несколько таких классов в .dll
библиотека, и те игроки могут поместить эти файлы модов в mod
подпапка, из которой будут загружены все классы модов при запуске игры.
Однако после такой загрузки моды не могут быть перезагружены, даже если я изменю DLL-файл и снова запустите скрипт импорта. Вот как выглядит скрипт импорта:
private static void DoInit()
{
avaliableCommands = new Dictionary<string, ICustomCommand>(); //static field
string[] files = Directory.GetFiles(@"ParticleFleet_Data\Managed\mods");
foreach (string file in files) //list all files in the mods directory
{
if (!file.ToLower().EndsWith(".dll"))
{
continue; //skip all non-dll files
}
try
{
byte[] bytes = File.ReadAllBytes(file);
string hash;
using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider())
{
hash = Convert.ToBase64String(sha1.ComputeHash(bytes));
}
Debug.LogFormat("Loading file {0}, hash: {1}", file, hash);
//load the .dll file as and assembly
Assembly assembly = Assembly.Load(bytes);
Type[] types = assembly.GetExportedTypes();
foreach (Type type in types) //list all classes from the library
{
if (typeof(ICustomCommand).IsAssignableFrom(type))
{
//add new command from the library
ICustomCommand command = (ICustomCommand)assembly.CreateInstance(type.FullName);
AddCommand(command); //this adds the command into the avaliableCommands field
Debug.LogFormat("Custom command {0} by {1} loaded, usage: {2}",
command.GetFunctionName(), command.GetAuthor(), command.GetFunctionSignature());
}
}
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
Debug.Log("Total mod commands loaded: " + avaliableCommands.Count);
}
Эта проблема
Теперь, наконец, мы подошли к интересной части: что работает / не работает в этом скрипте импорта:
- Сначала запустить правильно импортировать все классы из всех файлов
- При любом последующем запуске будут импортированы точно такие же ошибки, даже если файл.dll изменяется с новым содержимым (проверено с помощью хэш-журнала SHA1)
- Перезапуск игры после этого сделает эти изменения видимыми
- Удаление DLL-файла приведет к тому, что классы не загрузятся
- Удаление классов из файла.dll (перестройка.dll без / разных классов) не приведет к каким-либо изменениям - старые классы будут по-прежнему загружаться так, как если бы DLL не изменялся вообще.
- Переименование измененного.dll не помогает, старые классы по-прежнему загружаются
- Добавление новых классов в.dll не работает, только старые классы будут загружены
- Изменение пространств имен классов внутри.dll не работает
- Переименование классов внутри.dll не работает
- Однако изменение содержимого.dll действительно меняет хеш.
Посмотрев этот список, я могу прийти только к одному выводу: внутри DLL есть какой-то скрытый идентификатор, и загруженная сборка использует его для кэширования файлов DLL. Этот идентификатор, вероятно, похож на имя (с путем) проекта Visual Studio, в котором я создал библиотеку.dll.
Вопрос
Итак, мой вопрос: есть ли способ отключить это кэширование или генерировать разные идентификаторы каждый раз, когда я собираю библиотеку?
Я буду более чем рад изменять пространство имен классов каждый раз, когда сохраняю их, если возникнет другая проблема с загрузкой одного и того же класса несколько раз, но сейчас проблема в том, что Assembly.Load
загружает одну и ту же сборку снова и снова, независимо от того, какие данные я в нее добавляю.
И да, я пытался использовать AppDomain
, но создание одного сбоя, потому что это внутри проекта Unity, поэтому, если вы не знаете, как обойти эту проблему конкретно в Unity, я не могу использовать AppDomain
к несчастью.