Skip to content

Instantly share code, notes, and snippets.

@brettsam
Last active May 21, 2021 14:36
Show Gist options
  • Save brettsam/4426f3b2b55bc2d76792fac20a0919e3 to your computer and use it in GitHub Desktop.
Save brettsam/4426f3b2b55bc2d76792fac20a0919e3 to your computer and use it in GitHub Desktop.
ProxyBuilderRepro
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
namespace ProxyBuilderRepro
{
public class MyLoadContext : AssemblyLoadContext
{
public MyLoadContext(string name)
: base(name)
{
}
protected override Assembly Load(AssemblyName assemblyName)
{
var path = Path.Combine(Directory.GetCurrentDirectory(), $"{assemblyName.Name}.dll");
if (File.Exists(path))
{
return LoadFromAssemblyPath(path);
}
return Assembly.Load(assemblyName);
}
}
}
using System.Reflection;
namespace ProxyBuilderRepro
{
public class MyProxy : DispatchProxy
{
public static IMyInterface Create()
{
return Create<IMyInterface, MyProxy>();
}
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
return null;
}
}
public interface IMyInterface
{
}
}
using System;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
namespace ProxyBuilderRepro
{
class Program
{
static int _count = 0;
static void Main(string[] args)
{
// This highlights an issue that some WCF apps hit when running in Azure Functions. Functions uses
// a new AssemblyLoadContext whenever a customer makes a change that we're able to "hot-restart".
// The proxy generation for WCF fails the second time with an invalid cast because it is using an interface
// type from the previous AssemblyLoadContext.
var proxy1 = CreateProxyInNewContext();
object proxy2 = null;
try
{
// This throws an InvalidCastException: 'Unable to cast object of type 'generatedProxy_2' to type 'ProxyBuilderRepro.IMyInterface'.'
// It seems the ProxyBuilder uses the same Assembly to build each proxy, even though the second
// one is using a new AssemblyLoadContext. This means that the interfaces no longer match.
// Suspect: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs#L61
proxy2 = CreateProxyInNewContext();
}
catch
{
}
CheckContexts();
// This writes:
//
// Context: mine_0:
//
// ProxyBuilderRepro.IMyInterface(ProxyBuilderRepro), "mine_0" ProxyBuilderRepro.MyLoadContext #0
//
// Context: mine_1:
//
// ProxyBuilderRepro.IMyInterface(ProxyBuilderRepro), "mine_1" ProxyBuilderRepro.MyLoadContext #1
//
// Context: Default:
//
// ProxyBuilderRepro.IMyInterface(ProxyBuilderRepro), "Default" System.Runtime.Loader.DefaultAssemblyLoadContext #2
//
// generatedProxy_1(ProxyBuilder), "Default" System.Runtime.Loader.DefaultAssemblyLoadContext #2
// Interface implementations:
// ProxyBuilderRepro.IMyInterface(ProxyBuilderRepro), "mine_0" ProxyBuilderRepro.MyLoadContext #0
// generatedProxy_2(ProxyBuilder), "Default" System.Runtime.Loader.DefaultAssemblyLoadContext #2
// Interface implementations:
// ProxyBuilderRepro.IMyInterface(ProxyBuilderRepro), "mine_0" ProxyBuilderRepro.MyLoadContext #0 <-- This is wrong. Context should be mine_1.
}
private static object CreateProxyInNewContext()
{
var mine = new MyLoadContext($"mine_{_count++}");
var assm = mine.LoadFromAssemblyName(typeof(Program).Assembly.GetName());
var type = assm.GetType("ProxyBuilderRepro.MyProxy");
var create = type.GetMethod("Create", BindingFlags.Static | BindingFlags.Public);
return create.Invoke(null, null);
}
private static void CheckContexts()
{
// Look for the proxies in all contexts.
StringBuilder sb = new StringBuilder();
foreach (var context in AssemblyLoadContext.All)
{
sb.AppendLine();
sb.AppendLine($"Context: {context.Name}:");
foreach (var assembly in context.Assemblies)
{
foreach (var type in assembly.DefinedTypes)
{
if (type.FullName.Contains("generatedProxy"))
{
sb.AppendLine($" {type.FullName} ({assembly.GetName().Name}), {context}");
sb.AppendLine(" Interface implementations:");
foreach (var i in type.GetInterfaces())
{
var interfaceContext = AssemblyLoadContext.GetLoadContext(i.Assembly);
sb.AppendLine($" {i.FullName} ({i.Assembly.GetName().Name}), {interfaceContext}");
}
}
if (type.Name == "IMyInterface")
{
sb.AppendLine();
sb.AppendLine($" {type.FullName} ({assembly.GetName().Name}), {context}");
}
}
}
}
Console.WriteLine(sb.ToString());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment