.NETではAssemblyLoadContextを拡張することで、実行時にアセンブリを動的に読み込み・解放可能なプラグイン機構を構築できる。以下はその実装例である。
public class DynamicPluginContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver _dependencyResolver;
public DynamicPluginContext(string entryAssemblyPath) : base(isCollectible: true)
{
_dependencyResolver = new AssemblyDependencyResolver(entryAssemblyPath);
}
protected override Assembly? Load(AssemblyName requestedAssembly)
{
var resolvedPath = _dependencyResolver.ResolveAssemblyToPath(requestedAssembly);
return resolvedPath != null ? LoadFromAssemblyPath(resolvedPath) : null;
}
protected override IntPtr LoadUnmanagedDll(string nativeLibraryName)
{
var nativePath = _dependencyResolver.ResolveUnmanagedDllToPath(nativeLibraryName);
return nativePath != null ? LoadUnmanagedDllFromPath(nativePath) : IntPtr.Zero;
}
}
class PluginManager
{
private static AssemblyLoadContext? _currentContext;
static void LoadPlugin(string pluginPath)
{
UnloadCurrent();
var context = new DynamicPluginContext(pluginPath);
context.LoadFromAssemblyPath(pluginPath);
_currentContext = context;
}
static void UnloadCurrent()
{
_currentContext?.Unload();
}
static WeakReference CreatePluginInstance()
{
var targetType = _currentContext?.Assemblies
.Select(asm => asm.GetType("Plugin.Entity"))
.FirstOrDefault(t => t != null);
var instance = Activator.CreateInstance(targetType!);
return new WeakReference(instance);
}
static void Main()
{
LoadPlugin(@"path\to\plugin1.dll");
var entity1 = CreatePluginInstance();
LoadPlugin(@"path\to\plugin2.dll");
var entity2 = CreatePluginInstance();
Console.WriteLine(entity2.Target?.ToString());
for (int attempt = 0; attempt < 10 && entity1.IsAlive; attempt++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine($"GC Round {attempt + 1}: Alive = {entity1.IsAlive}");
}
}
}
この仕組みにより、ゲームのホットアップデートや拡張性を重視するアプリケーションにおいて、再起動なしにモジュールを更新・交換することが可能となる。