Reputation: 1031
Demorepo:
https://github.com/gabbersepp/csharp-dynamic-replace-class
How to use:
Old SO post:
Replace existing class definition at runtime with newly created type
Given:
A class in a lib:
namespace Test.TestLib
{
public class Class1
{
}
}
And a second class which creates an instance of it:
namespace console
{
public class AnotherClass
{
public void Create()
{
new Class1();
}
}
}
And a console app that calls create
:
static void Main(string[] args)
{
//...
new AnotherClass().Create();
}
Please keep in mind that only Class1
is in an extra lib. The other two classes are in the same.
What I want to do:
Replace the Assembly at runtime:
AssemblyName dynamicAssemblyName = new AssemblyName("TestLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
dynamicAssembly =
AssemblyBuilder.DefineDynamicAssembly(dynamicAssemblyName, AssemblyBuilderAccess.Run);
var dynamicModule = dynamicAssembly.DefineDynamicModule("TestLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
But I want not provide the type at this time. Instead I use:
AppDomain.CurrentDomain.TypeResolve += CurrentDomain_TypeResolve;
private static Assembly CurrentDomain_TypeResolve(object sender, ResolveEventArgs args)
{
Console.WriteLine("resolve type");
if (args.Name.Contains("TestLib"))
{
dynamicModule.DefineType("Test.TestLib.Class1", TypeAttributes.Class | TypeAttributes.Public).CreateType();
return dynamicAssembly;
}
return null;
}
Problem:
The event is not called when the line new AnotherClass().Create();
is executed. Instead an exception is thrown:
System.TypeLoadException: Der Typ "Test.TestLib.Class1" in der Assembly "TestLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" konnte nicht geladen werden
something like:
System.TypeLoadException: The type "Test.TestLib.Class1" in the assembly "TestLib, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null" could not be loaded
Please have a look into the repo for a full example.
//Edit: The demo project is written with VS2019 für .net461. I think the main concepts are the same for dotnet core. Let me know if you'd rather work with dotnet core so I can provide the project for both platforms.
//Edit2:
I debugged into the IL Code and saw, that everything runs fine until the constructor of Class1
is called:
So personally I don't think that the event handler is plugged into too late as stated by Bruno.
The official documentation states, that this event is called if the assembly is unknown:
https://learn.microsoft.com/de-de/dotnet/api/system.appdomain.typeresolve?view=netframework-4.8 The TypeResolve event occurs when the common language runtime is unable to determine the assembly that can create the requested type
I did not read that before. Hopefully someone can help me :-)
//Edit3 - possible solution:
A workaround could be, to create the types based on a list of class names. To not loose compile safety, I can use nameof
which produces no IL code. An example can be found in the repo in the branch resolveType-solution1
. But of course, not the solution I am looking for :-(
Upvotes: 0
Views: 469
Reputation: 2357
I am not 100% sure but I think I figured out what is going on.
When running your code, your main is evaluated by the JIT before being executed. This results in an attempt to load AnotherClass and it's dependecies (as everything is very likely to be inlined in the end because it's very small).
This means that you won't have the opportunity to run this code before .net tries to find your type:
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
AppDomain.CurrentDomain.TypeResolve += CurrentDomain_TypeResolve;
I managed to bypass this limitation by not mentionning the class in your main by replacing:
new AnotherClass().Create();
by:
var type = Type.GetType("AnotherClass");
var method = type.GetMethod("Create");
var instance = type.GetConstructor(new Type[] { }).Invoke(new object[0]);
method.Invoke(instance, new object[0]);
The trick is deferring the load of the type after your callbacks have been plugged in the AppDomain. By using reflection I mask the Types really used to the JIT until this code is actually called.
Take away: you don't really know when your type will be loaded (in fact as soon as JIT reads code mentioning your type). This means you have to plug your callback as soon as possible and defer as much as possible the inclusion of code mentioning your type.
Upvotes: 1
Reputation: 1664
calling Type.GetType("Test.TestLib.Class1");
would invoke AppDomain.TypeResolve but not AppDomain.AssemblyResolve, not sure why though
Upvotes: 0