0

Demorepo:

https://github.com/gabbersepp/csharp-dynamic-replace-class

How to use:

  1. Checkout
  2. Compile
  3. Delete TestLib.dll & TestLib.pdb from console/bin/Debug
  4. Execute console.exe through cmd

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: enter image description here 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 :-(

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Josef Biehler
  • 952
  • 4
  • 16

2 Answers2

1

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.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Bruno Belmondo
  • 2,299
  • 8
  • 18
  • The AssemblyResolve callback is plugged in early enough, this can be seen on the console when the debug messages are printed. I debugged into the IL code and everything is running fine until the constructor of Class1 is called. Please see this: https://imgur.com/FxhxmCm – Josef Biehler Jul 09 '19 at 09:48
  • I am not sure about the internals of type resolving. Maybe after the loading of the assembly the CLR knows which types are contained and does not request it a second time? I have heard something about static assemblies vs. dynamic assemblies. But for the second one, type resolving by event should work. :-( – Josef Biehler Jul 09 '19 at 09:55
0

calling Type.GetType("Test.TestLib.Class1"); would invoke AppDomain.TypeResolve but not AppDomain.AssemblyResolve, not sure why though

Sohaib Jundi
  • 1,576
  • 2
  • 7
  • 15