C#反射-如何在运行时重新加载类?

时间:2019-04-03 20:19:44

标签: c# reflection .net-assembly

当前,我正在C#中的一个项目中进行反射。我已经使用GUI创建了WPF应用程序。该GUI包含一个 组合框,其中包含实现特定接口的所有类名。具有显示的类名的类位于相同的解决方案中。 组合框旁边是一个按钮,用于刷新组合框中的内容。但是,当我运行应用程序时,请修改实现该接口的类名,然后 单击该刷新按钮,更改不会显示在组合框中。例如,当我更改一个类名时,它应该显示新的类名,而不是旧的类名。

我已经提取了项目的这一部分,以便在一个空的控制台应用程序中对其进行测试。这里我有一个由类实现的接口 QuickSortAlgorithm,DynamicSortAlgorithm和MergeSortAlgorithm。接下来,我在主类中编写了以下简单直接的代码。

    public static List<string> AlgoList = new List<string>();

    static void Main(string[] args) {
        RefreshAlgorithms();
        Print();

        Console.WriteLine("\nChange a classname and press a key \n");
        Console.ReadKey();

        Print();

        Console.WriteLine("\nPress a key to exit the program \n");
        Console.ReadKey();
    }

    private static void RefreshAlgorithms() {
        AlgoList.Clear();
        Type AlgorithmTypes = typeof(IAlgorithms);
        foreach (var type in Assembly.GetCallingAssembly().GetTypes()) {
            if (AlgorithmTypes.IsAssignableFrom(type) && (type != AlgorithmTypes)) {
                AlgoList.Add(type.Name);
            }
        }
    }

    private static void Print() {
        Console.WriteLine("Algorithm classes:");
        foreach (var Algo in AlgoList) {
            Console.WriteLine(Algo);
        }
    }

当我运行该应用程序时,看到打印了类名QuickSortAlgorithm,DynamicSortAlgorithm和MergeSortAlgorithm。但是,例如,如果我更改名称, 将QuickSortAlgorithm类转换为QuickSortAlgorithmmmmm我希望它在我按下一个键时就打印QuickSortAlgorithmmmmm。但是,情况并非如此,名称 QuickSortAlgorithm仍在显示。

我觉得我在反思的概念中忽略了某些东西。建立解决方案后还能做到吗?如果我正确理解这一概念,则可以在运行时实现更改。我知道 这会使我的应用程序运行缓慢得多,但我真的很想学习更多有关此概念的信息。如果可以向我解释我在做错什么,我将非常高兴。

3 个答案:

答案 0 :(得分:1)

不幸的是,这不起作用。程序集加载后,它将保持原样加载,仅在重新启动应用程序时应用更改。

如果使用的是.NET Framework,则可以创建一个新的AppDomain并将程序集加载到此AppDomain中。完成后,您可以卸载AppDomain及其组件。您可以在正在运行的应用程序中执行多次。

void RefreshAlgorithms()
{
    var appDomain = AppDomain.CreateDomain("TempDomain");
    appDomain.Load(YourAssembly);
    appDomain.DoCallback(Inspect);
    AppDomain.Unload(appDomain);
}

void Inspect()
{
    // This runs in the new appdomain where you can inspect your classes
}

但是请小心,因为使用AppDomains会产生一些警告,例如在与AppDomain进行通信时需要使用远程处理。

据我所知,.NET Core中没有可用的方法

答案 1 :(得分:1)

一旦将已编译的.NET程序集加载到应用程序中,就不能在不重新启动和重建应用程序的情况下对该程序集中的类型进行进一步的更改。如果允许这样做,则可能导致各种奇怪的行为。例如,假设应用程序中的List<Foo>填充了3个foo,然后将Foo.Idint更改为string。实时数据应该怎么处理?

但是,如果您进行反射的应用程序与被反射的程序集不同,则可以进行设置,以便您可以监视对该程序集文件的更改并重新进行反射。关键是放弃System.Reflection(仅适用于已加载的程序集),而应使用Mono.Cecil library

Cecil读入程序集元数据时不会将代码加载到应用程序中,因此它在“仅反射”用例下效果很好。当然,它实际上不能调用代码。 Cecil API与System.Reflection包含许多相似之处。例如:

var assembly = Mono.Cecil.AssemblyDefinition.ReadAssembly(Path.Combine(projectDirectory, "bin", "Debug", "Something.dll"));
var controllerTypes = assembly.MainModule.Types.Where(t => t.BaseType?.FullName == "System.Web.Mvc.Controller")
    .ToArray();

另一个注意事项是.NET Framework(不是.NET Core)包含AppDomains的概念,可以将其加载或卸载。它们在一个过程中就像.NET的“子过程”一样工作,并且具有关于可以跨越其边界的规则。如果确实需要重新加载代码并执行代码,那么这可能是一个解决方案。

另一种选择可能是Roslyn脚本API,如果您要动态加载和执行源代码(相对于已编译的程序集),则该API会很好地工作。

答案 2 :(得分:0)

您似乎忽略了一个小步骤:​​构建代码。将类重命名为QuickSortAlgorithmmmm后,需要保存并构建该程序集。

这样做将重新创建程序集(假设您的应用程序没有打开的句柄)。之后,单击刷新按钮应显示新名称。

如果由于其中也包含您的GUI代码而无法重新加载该程序集(正在运行),则可能需要将实现该接口的类分离到自己的程序集中,并可能单独构建该程序集并进行复制将其移到您的应用可以找到的目录中(例如,在Plugins目录中)。