|
| 1 | +using System.Reflection; |
| 2 | +using System.Runtime.Loader; |
| 3 | +using Lib; |
| 4 | +// The APP was compiled against Lib A (Make() returns a fresh owned Res each call) and disposes each result: correct |
| 5 | +// under A. If the runtime substitutes a same-version patch (B) or a major bump (C) whose Make() returns a shared |
| 6 | +// cached Res, the second Make() hands back an already-disposed object: the effective semantics differ. |
| 7 | +static string Facets(Assembly a) |
| 8 | +{ |
| 9 | + var n = a.GetName(); |
| 10 | + var info = a.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion; |
| 11 | + return $"name={n.Name} version={n.Version} pkt={Convert.ToHexString(n.GetPublicKeyToken() ?? Array.Empty<byte>()).ToLowerInvariant()} mvid={a.ManifestModule.ModuleVersionId} info={info} path={a.Location}"; |
| 12 | +} |
| 13 | +var mode = args.Length > 0 ? args[0] : "direct"; |
| 14 | +if (mode == "direct") |
| 15 | +{ |
| 16 | + var a = Factory.Make(); a.Dispose(); |
| 17 | + var b = Factory.Make(); |
| 18 | + Console.WriteLine($"[direct] loaded: {Facets(typeof(Factory).Assembly)}"); |
| 19 | + Console.WriteLine($"[direct] flavor={Factory.Flavor} second_make_already_disposed={b.Disposed} same_instance={ReferenceEquals(a, b)} live={Res.Live}"); |
| 20 | + b.Dispose(); |
| 21 | +} |
| 22 | +else |
| 23 | +{ |
| 24 | + // plugin-style load through a custom AssemblyLoadContext + AssemblyDependencyResolver rooted at a plugin dir |
| 25 | + var dir = Path.GetFullPath(args[1]); |
| 26 | + var alc = new PluginContext(Path.Combine(dir, "Lib.dll")); |
| 27 | + var asm = alc.LoadFromAssemblyName(new AssemblyName("Lib")); |
| 28 | + var factory = asm.GetType("Lib.Factory")!; |
| 29 | + var make = factory.GetMethod("Make")!; |
| 30 | + var r1 = (IDisposable)make.Invoke(null, null)!; r1.Dispose(); |
| 31 | + var r2 = make.Invoke(null, null)!; |
| 32 | + var disposed = (bool)r2.GetType().GetProperty("Disposed")!.GetValue(r2)!; |
| 33 | + Console.WriteLine($"[plugin] loaded: {Facets(asm)}"); |
| 34 | + Console.WriteLine($"[plugin] flavor={factory.GetProperty("Flavor")!.GetValue(null)} second_make_already_disposed={disposed} same_instance={ReferenceEquals(r1, r2)} default_alc_lib={Facets(typeof(Factory).Assembly)}"); |
| 35 | +} |
| 36 | +sealed class PluginContext : AssemblyLoadContext |
| 37 | +{ |
| 38 | + readonly AssemblyDependencyResolver _resolver; |
| 39 | + public PluginContext(string mainAssembly) : base(isCollectible: true) => _resolver = new AssemblyDependencyResolver(mainAssembly); |
| 40 | + protected override Assembly? Load(AssemblyName name) |
| 41 | + { |
| 42 | + var p = _resolver.ResolveAssemblyToPath(name); |
| 43 | + return p is null ? null : LoadFromAssemblyPath(p); |
| 44 | + } |
| 45 | +} |
0 commit comments